Что такое принцип обращения зависимостей и почему он важен?

178

Что такое принцип обращения зависимостей и почему он важен?

Филипп Уэллс
источник
3
Смешное количество ответов здесь с использованием терминов «высокий уровень» и «низкий уровень» из Википедии. Эти условия недоступны и ведут множество читателей на эту страницу. Если вы собираетесь извергать Википедию, определите эти термины в своих ответах, чтобы дать контекст!
8bitjunkie

Ответы:

107

Проверьте этот документ: Принцип инверсии зависимостей .

Это в основном говорит:

  • Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.
  • Абстракции никогда не должны зависеть от деталей. Детали должны зависеть от абстракций.

Что касается того, почему это важно, вкратце: изменения рискованны, и, полагаясь на концепцию, а не на реализацию, вы уменьшаете необходимость изменений на сайтах вызовов.

По сути, DIP уменьшает связь между различными частями кода. Идея заключается в том, что, хотя существует множество способов реализации, скажем, средства ведения журнала, способ его использования должен быть относительно стабильным во времени. Если вы можете извлечь интерфейс, который представляет концепцию ведения журнала, этот интерфейс должен быть гораздо более стабильным во времени, чем его реализация, и на сайты вызовов должно быть значительно меньше под влиянием изменений, которые вы могли бы внести при поддержке или расширении этого механизма ведения журнала.

Также делая зависимость реализации от интерфейса, вы получаете возможность выбирать во время выполнения, какая реализация лучше подходит для вашей конкретной среды. В зависимости от случаев это тоже может быть интересно.

Карл Селеборг
источник
31
Этот ответ не говорит, почему DIP важен, или даже что такое DIP. Я прочитал официальный документ DIP и думаю, что это действительно плохой и неоправданный «принцип», потому что он основан на ошибочном предположении: модули высокого уровня можно использовать повторно.
Rogério
3
Рассмотрим граф зависимости для некоторых объектов. Примените DIP к объектам. Теперь любой объект будет зависеть от реализации других объектов. Модульное тестирование теперь просто. Позже возможен рефакторинг для повторного использования. Изменения дизайна имеют очень ограниченные возможности изменения. Проблемы с дизайном не касаются. Смотрите также схему AI "Blackboard" для инверсии зависимости данных. Вместе очень мощные инструменты делают программное обеспечение понятным, обслуживаемым и надежным. Игнорировать внедрение зависимостей в этом контексте. Это не связано.
Тим Уиллискрофт
6
Класс A, использующий класс B, не означает, что A «зависит» от реализации B; это зависит только от публичного интерфейса B. Добавление отдельной абстракции, от которой теперь зависят и A, и B, означает лишь то, что A больше не будет зависеть от времени компиляции от открытого интерфейса B. Изоляция между модулями может быть легко достигнута без этих дополнительных абстракций; Для этого в Java и .NET есть специальные инструменты для пересмешки, которые имеют дело со всеми ситуациями (статические методы, конструкторы и т. д.). Применение DIP делает программное обеспечение более сложным, менее обслуживаемым и не более тестируемым.
Rogério
1
Тогда что такое инверсия контроля? способ достижения инверсии зависимости?
user20358 26.09.12
2
@Rogerio, смотрите ответ Дерека Грира ниже, он объясняет это. AFAIK, DIP говорит, что именно A обязывает то, что нужно A, а не B говорит то, что нужно A. Таким образом, интерфейс, в котором нуждается A, должен быть задан не B, а для A.
ejaenv 13.12.12
145

Книги Agile Software Development, Принципы, Шаблоны и Практики и Agile Принципы, Шаблоны и Практики в C # являются лучшими ресурсами для полного понимания первоначальных целей и мотивов, лежащих в основе Принципа инверсии зависимостей. Статья «Принцип обращения зависимостей» также является хорошим ресурсом, но из-за того, что она является сжатой версией черновика, который в конечном итоге попал в ранее упомянутые книги, она оставляет некоторые важные дискуссии о концепции владение пакетами и интерфейсами, которые являются ключевыми для отличия этого принципа от более общего совета «программировать для интерфейса, а не реализации», который можно найти в книге «Шаблоны проектирования» (Gamma и др.).

Для краткого изложения принцип инверсии зависимостей в первую очередь направлен на изменение традиционного направления зависимостей от компонентов «более высокого уровня» к компонентам «более низкого уровня», так что компоненты «более низкого уровня» зависят от интерфейсов, принадлежащих компонентам «более высокого уровня» , (Примечание. Компонент «более высокого уровня» здесь относится к компоненту, требующему внешних зависимостей / сервисов, а не обязательно к его концептуальному положению в многоуровневой архитектуре.) При этом связь не уменьшается настолько, насколько она смещается от компонентов, которые теоретически менее ценным для компонентов, которые теоретически более ценны.

Это достигается путем разработки компонентов, чьи внешние зависимости выражаются в виде интерфейса, для которого потребитель компонента должен предоставить реализацию. Другими словами, определенные интерфейсы выражают то, что нужно компоненту, а не то, как вы используете компонент (например, «INeedSomething», а не «IDoSomething»).

То, на что не ссылается Принцип инверсии зависимостей, - это простая практика абстрагирования зависимостей с помощью интерфейсов (например, MyService → [ILogger ⇐ Logger]). Хотя это отделяет компонент от конкретной детали реализации зависимости, оно не инвертирует отношения между потребителем и зависимостью (например, [MyService → IMyServiceLogger] ⇐ Logger.

Важность принципа инверсии зависимости может быть сведена к единственной цели - возможности повторно использовать программные компоненты, которые полагаются на внешние зависимости для части их функциональных возможностей (регистрация, проверка и т. Д.)

В рамках этой общей цели повторного использования мы можем выделить два подтипа повторного использования:

  1. Использование программного компонента в нескольких приложениях с реализациями зависимостей (например, вы разработали DI-контейнер и хотите обеспечить ведение журнала, но не хотите связывать свой контейнер с определенным регистратором, так что каждый, кто использует ваш контейнер, должен также использовать выбранную вами библиотеку журналов).

  2. Использование программных компонентов в развивающемся контексте (например, вы разработали компоненты бизнес-логики, которые остаются неизменными в разных версиях приложения, где детали реализации развиваются).

В первом случае повторного использования компонентов в нескольких приложениях, например, с библиотекой инфраструктуры, цель состоит в том, чтобы предоставить потребителям базовую инфраструктуру, не привязывая ваших потребителей к зависимостям вашей собственной библиотеки, поскольку для получения зависимостей от таких зависимостей требуется потребителям также требуются такие же зависимости. Это может быть проблематично, когда потребители вашей библиотеки решают использовать другую библиотеку для тех же потребностей инфраструктуры (например, NLog и log4net), или если они решают использовать более позднюю версию необходимой библиотеки, которая не имеет обратной совместимости с версией требуется вашей библиотекой.

Во втором случае повторного использования компонентов бизнес-логики (т. Е. «Компонентов более высокого уровня») цель состоит в том, чтобы изолировать реализацию вашего приложения в основной области от меняющихся потребностей ваших деталей реализации (например, изменение / обновление постоянных библиотек, библиотек обмена сообщениями). стратегии шифрования и т. д.). В идеале изменение деталей реализации приложения не должно нарушать компоненты, инкапсулирующие бизнес-логику приложения.

Примечание. Некоторые могут возражать против описания этого второго случая как фактического повторного использования, полагая, что такие компоненты, как компоненты бизнес-логики, используемые в одном развивающемся приложении, представляют собой только одно использование. Идея здесь заключается в том, что каждое изменение в деталях реализации приложения отображает новый контекст и, следовательно, другой вариант использования, хотя конечные цели можно отличить как изоляция и переносимость.

Хотя следование принципу инверсии зависимостей во втором случае может принести некоторую пользу, следует отметить, что его значение применительно к современным языкам, таким как Java и C #, значительно снижено, возможно, до такой степени, что оно не имеет значения. Как обсуждалось ранее, DIP включает в себя полное разделение деталей реализации на отдельные пакеты. В случае развивающегося приложения, однако, простое использование интерфейсов, определенных в бизнес-сфере, защитит от необходимости модифицировать компоненты более высокого уровня из-за меняющихся потребностей компонентов детализации реализации, даже если детали реализации в конечном итоге находятся в одном пакете. , Эта часть принципа отражает аспекты, которые имели отношение к языку в момент его кодификации (например, C ++), которые не имеют отношения к более новым языкам. Это сказало,

Более подробное обсуждение этого принципа, поскольку оно касается простого использования интерфейсов, внедрения зависимостей и шаблона разделенного интерфейса, можно найти здесь . Кроме того, обсуждение того, как этот принцип относится к динамически типизированным языкам, таким как JavaScript, можно найти здесь .

Дерек Грир
источник
17
Спасибо. Теперь я вижу, как мой ответ не соответствует сути. Разница между MyService → [ILogger ⇐ Logger] и [MyService → IMyServiceLogger] ⇐ Logger неуловима, но важна.
Патрик МакЭлхани
2
В той же строке это очень хорошо объяснено здесь: lostechies.com/derickbailey/2011/09/22/…
ejaenv 13.12.12
1
Как бы я хотел, чтобы люди перестали использовать логгеры в качестве канонического варианта использования для внедрения зависимостей. Особенно в связи с log4net, где его почти анти-шаблон. Это в сторону, офигенное объяснение!
Каспер Леон Нильсен
4
@ Каспер Леон Нильсен - DIP не имеет ничего общего с DI. Они не синонимы и не эквивалентные понятия.
TSmith
4
@ VF1 Как указано в сводном параграфе, важность принципа инверсии зависимости в первую очередь заключается в повторном использовании. Если Джон выпускает библиотеку, которая имеет внешнюю зависимость от библиотеки журналов, и Сэм желает использовать библиотеку Джона, Сэм принимает зависимость от временных журналирования. Сэм никогда не сможет развернуть свое приложение без библиотеки журналов, которую выбрал Джон. Однако если Джон следует DIP, Сэм может предоставить адаптер и использовать любую библиотеку журналов, которую он выберет. DIP - это не удобство, а связь.
Дерек Грир
14

При разработке программных приложений мы можем рассматривать классы низкого уровня, классы, которые реализуют базовые и основные операции (доступ к диску, сетевые протоколы, ...), а классы высокого уровня - классы, которые инкапсулируют сложную логику (бизнес-потоки, ...).

Последние полагаются на классы низкого уровня. Естественным способом реализации таких структур было бы написание классов низкого уровня и, как только мы их получили, написание сложных классов высокого уровня. Поскольку классы высокого уровня определяются в терминах других, это кажется логичным способом сделать это. Но это не гибкий дизайн. Что произойдет, если нам нужно заменить класс низкого уровня?

Принцип обращения зависимостей гласит:

  • Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Этот принцип стремится «инвертировать» традиционное представление о том, что модули высокого уровня в программном обеспечении должны зависеть от модулей более низкого уровня. Здесь высокоуровневые модули владеют абстракцией (например, выбирая методы интерфейса), которые реализуются низкоуровневыми модулями. Таким образом, делая модули более низкого уровня зависимыми от модулей более высокого уровня.

nikhil.singhal
источник
11

Эффективное применение инверсии зависимостей дает гибкость и стабильность на уровне всей архитектуры вашего приложения. Это позволит вашему приложению развиваться более безопасно и стабильно.

Традиционная многоуровневая архитектура

Традиционно пользовательский интерфейс многоуровневой архитектуры зависел от бизнес-уровня, а это, в свою очередь, зависело от уровня доступа к данным.

Вы должны понимать слой, пакет или библиотеку. Посмотрим, как будет выглядеть код.

У нас была бы библиотека или пакет для слоя доступа к данным.

// DataAccessLayer.dll
public class ProductDAO {

}

И другая бизнес-логика уровня библиотеки или пакета, которая зависит от уровня доступа к данным.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Многоуровневая архитектура с инверсией зависимостей

Инверсия зависимости указывает на следующее:

Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.

Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Что такое модули высокого уровня и низкого уровня? Мышление модулей, таких как библиотеки или пакеты, высокоуровневыми модулями будут те, которые традиционно имеют зависимости и низкоуровневые, от которых они зависят.

Другими словами, высокий уровень модуля будет там, где вызывается действие, и низкий уровень, где действие выполняется.

Из этого принципа можно сделать разумный вывод: между конкрециями не должно быть зависимости, но должна быть зависимость от абстракции. Но в соответствии с подходом, который мы используем, мы можем неправильно использовать зависимость от инвестиций, но это абстракция.

Представьте, что мы адаптируем наш код следующим образом:

У нас была бы библиотека или пакет для уровня доступа к данным, который определяет абстракцию.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

И другая бизнес-логика уровня библиотеки или пакета, которая зависит от уровня доступа к данным.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

Хотя мы зависим от абстракции, зависимость между бизнесом и доступом к данным остается неизменной.

Чтобы получить инверсию зависимостей, интерфейс персистентности должен быть определен в модуле или пакете, где находится логика или домен высокого уровня, а не в модуле низкого уровня.

Сначала определите, что такое уровень домена, и абстракция его связи определяется постоянством.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

После того, как уровень персистентности зависит от домена, теперь можно инвертировать, если определена зависимость.

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(источник: xurxodev.com )

Углубление принципа

Важно хорошо усвоить концепцию, углубить цель и выгоды. Если мы останемся в механике и изучим типичный репозиторий, мы не сможем определить, где мы можем применить принцип зависимости.

Но почему мы инвертируем зависимость? Какова основная цель за пределами конкретных примеров?

Такое обычно позволяет наиболее стабильным вещам, которые не зависят от менее стабильных вещей, меняться чаще.

Тип персистентности легче изменить, либо база данных или технология для доступа к той же базе данных, чем логика домена или действия, предназначенные для связи с постоянством. Из-за этого зависимость меняется на противоположную, потому что легче изменить постоянство, если это изменение произойдет. Таким образом, нам не придется менять домен. Доменный слой является наиболее стабильным из всех, поэтому он не должен зависеть ни от чего.

Но есть не только этот пример хранилища. Существует много сценариев, в которых применяется этот принцип, и существуют архитектуры, основанные на этом принципе.

архитектуры

Существуют архитектуры, в которых инверсия зависимостей является ключом к ее определению. Во всех доменах это наиболее важно, и именно абстракции будут указывать протокол связи между доменом и остальными пакетами или библиотеками.

Чистая Архитектура

В чистой архитектуре домен расположен в центре, и если вы посмотрите в направлении стрелок, указывающих на зависимость, станет ясно, какие слои являются наиболее важными и стабильными. Наружные слои считаются нестабильными инструментами, поэтому избегайте зависимости от них.


(источник: 8thlight.com )

Шестиугольная архитектура

То же самое происходит с гексагональной архитектурой, где домен также расположен в центральной части, а порты являются абстракциями коммуникации от внешнего мира домино. Здесь снова очевидно, что область является наиболее устойчивой и традиционная зависимость является инвертированной.

xurxodev
источник
Я не уверен, что это значит, но это предложение непоследовательно: «Но в соответствии с подходом, который мы используем, мы можем неправильно использовать зависимость от инвестиций, но это абстракция». Возможно, вы хотели бы исправить?
Марк Амери
9

Для меня Принцип инверсии зависимостей, описанный в официальной статье , на самом деле является ошибочной попыткой увеличить возможность повторного использования модулей, которые по своей природе являются менее многократно используемыми, а также способом обойти проблему в языке C ++.

Проблема в C ++ состоит в том, что заголовочные файлы обычно содержат объявления закрытых полей и методов. Поэтому, если модуль высокого уровня C ++ включает заголовочный файл для модуля низкого уровня, это будет зависеть от фактической реализации деталей этого модуля. И это, очевидно, не очень хорошая вещь. Но это не проблема в более современных языках, обычно используемых сегодня.

Модули высокого уровня по своей природе менее пригодны для повторного использования, чем модули низкого уровня, поскольку первые обычно более специфичны для приложения / контекста, чем последние. Например, компонент, который реализует экран пользовательского интерфейса, имеет самый высокий уровень и также очень (полностью?) Специфичен для приложения. Попытка повторно использовать такой компонент в другом приложении неэффективна и может привести только к чрезмерной разработке.

Таким образом, создание отдельной абстракции на том же уровне компонента A, который зависит от компонента B (который не зависит от A), может быть выполнено, только если компонент A действительно будет полезен для повторного использования в различных приложениях или контекстах. Если это не так, то применение DIP было бы плохим дизайном.

Рожерио
источник
Даже если высокоуровневая абстракция полезна только в контексте этого приложения, она может иметь значение, если вы хотите заменить реальную реализацию заглушкой для тестирования (или предоставить интерфейс командной строки для приложения, обычно доступного в Интернете)
Przemek Покрывка
3
DIP важен для разных языков и не имеет ничего общего с самим C ++. Даже если ваш код высокого уровня никогда не покинет ваше приложение, DIP позволяет вам содержать изменения кода при добавлении или изменении кода более низкого уровня. Это снижает как расходы на техническое обслуживание, так и непреднамеренные последствия изменений. DIP - это концепция более высокого уровня. Если вы не понимаете этого, вам нужно больше гуглить.
Дирк Бестер
3
Я думаю, вы не поняли, что я сказал о C ++. Это была просто мотивация для DIP; без сомнения, это более общее, чем это. Обратите внимание, что официальная статья о DIP проясняет, что главной мотивацией была поддержка повторного использования модулей высокого уровня, делая их невосприимчивыми к изменениям в модулях низкого уровня; без необходимости повторного использования, то, скорее всего, это будет излишним и чрезмерно инженерным. (Вы читали это? Это также говорит о проблеме C ++.)
Rogério
1
Разве вы не пропускаете обратное применение DIP? То есть модули более высокого уровня реализуют ваше приложение, по сути. Ваша главная задача не в том, чтобы повторно использовать его, а в том, чтобы сделать его менее зависимым от реализаций низкоуровневых модулей, чтобы его обновление в соответствии с будущими изменениями изолировало ваше приложение от разрушительного воздействия времени и новых технологий. Это не облегчает замену WindowsOS, это делает WindowsOS менее зависимой от деталей реализации FAT / HDD, чтобы новые NTFS / SSD могли быть подключены к WindowsOS без каких-либо последствий.
knockNrod
1
Экран пользовательского интерфейса определенно не является модулем высшего уровня или не должен быть для любого приложения. Модули высшего уровня - это те, которые содержат бизнес-правила. Модуль самого высокого уровня также не определяется его возможностью повторного использования. Пожалуйста , проверьте простое объяснение дяди Боба в этой статье: blog.cleancoder.com/uncle-bob/2016/01/04/...
Хумбаб
8

В основном это говорит:

Класс должен зависеть от абстракций (например, интерфейс, абстрактные классы), а не от конкретных деталей (реализации).

martin.ra
источник
Это может быть так просто? Есть длинные статьи и даже книги, как упомянул Дерек Греер? Я действительно искал простой ответ, но это невероятно, если это так просто: D
Darius.V
1
@ darius-v это не другая версия высказывания "используйте абстракции". Это о том, кто владеет интерфейсами. Принципал говорит, что клиент (компоненты более высокого уровня) должен определять интерфейсы, а компоненты более низкого уровня должны их реализовывать.
Боран
6

Хорошие ответы и хорошие примеры уже даны здесь другими.

Причина, по которой DIP важен, заключается в том, что он обеспечивает ОО-принцип «слабо связанной конструкции».

Объекты в вашем программном обеспечении НЕ должны попадать в иерархию, где некоторые объекты являются объектами верхнего уровня, в зависимости от объектов низкого уровня. Изменения в объектах нижнего уровня будут затем распространяться на ваши объекты верхнего уровня, что делает программное обеспечение очень хрупким для изменений.

Вы хотите, чтобы ваши объекты «верхнего уровня» были очень стабильными и не хрупкими для изменений, поэтому вам нужно инвертировать зависимости.

Hace
источник
6
Как именно DIP делает это для кода Java или .NET? В этих языках, в отличие от C ++, изменения в реализации модуля низкого уровня не требуют изменений в модулях высокого уровня, которые его используют. Происходят только изменения в общедоступном интерфейсе, но тогда абстракция, определенная на более высоком уровне, также должна будет измениться.
Rogério
5

Гораздо более ясный способ сформулировать принцип обращения зависимостей:

Ваши модули, которые инкапсулируют сложную бизнес-логику, не должны напрямую зависеть от других модулей, которые инкапсулируют бизнес-логику. Вместо этого они должны зависеть только от интерфейсов для простых данных.

Т.е. вместо реализации вашего класса, Logicкак обычно делают люди:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

Вы должны сделать что-то вроде:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Dataи DataFromDependencyдолжен жить в том же модуле, что Logicи не с Dependency.

Зачем это делать?

  1. Два модуля бизнес-логики теперь разъединены. Когда Dependencyменяется, вам не нужно менять Logic.
  2. Понимание того, что Logicделает, является намного более простой задачей: это работает только на том, что похоже на ADT.
  3. Logicтеперь можно легче тестировать. Теперь вы можете напрямую создавать экземпляры Dataс поддельными данными и передавать их. Нет необходимости в макетах или сложных тестовых лесах.
mattvonb
источник
Я не думаю, что это правильно. Если DataFromDependency, который непосредственно ссылается Dependency, находится в том же модуле, что и модуль Logic, то Logicмодуль все еще напрямую зависит от Dependencyмодуля во время компиляции. Согласно объяснению принципа дядей Бобом , избегая всего этого, DIP. Скорее, чтобы следовать DIP, Dataдолжен быть в том же модуле, что и Logic, но DataFromDependencyдолжен быть в том же модуле, что и Dependency.
Марк Амери
3

Инверсия управления (IoC) - это шаблон проектирования, в котором объект получает свою зависимость от внешней структуры, а не запрашивает структуру для своей зависимости.

Пример псевдокода с использованием традиционного поиска:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Аналогичный код с использованием IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

Преимущества IoC:

  • У вас нет зависимости от центральной структуры, поэтому при желании это можно изменить.
  • Поскольку объекты создаются путем внедрения, предпочтительно с использованием интерфейсов, легко создавать модульные тесты, которые заменяют зависимости фиктивными версиями.
  • Развязка кода.
Staale
источник
Принцип инверсии зависимости и инверсия контроля - это одно и то же?
Питер Мортенсен
3
«Инверсия» в DIP является не то же самое , как и в инверсии управления. Первый - о зависимостях времени компиляции, а второй - о потоке управления между методами во время выполнения.
Рожерио
Я чувствую, что в версии IoC чего-то не хватает. Как какой объект базы данных он определяет, откуда он берется. Я пытаюсь понять, как это не просто задерживает указание всех зависимостей на верхний уровень в зависимости от гигантского бога
Ричард Тингл
1
Как уже сказал @ Rogério, DIP - это не DI / IoC. Этот ответ неверен IMO
zeraDev
1

Точка инверсии зависимостей заключается в создании программного обеспечения многократного использования.

Идея состоит в том, что вместо двух частей кода, полагающихся друг на друга, они полагаются на какой-то абстрактный интерфейс. Тогда вы можете повторно использовать любой кусок без другого.

Чаще всего это достигается путем использования контейнера инверсии управления (IoC), такого как Spring в Java. В этой модели свойства объектов устанавливаются через XML-конфигурацию, а не объекты выходят и находят свою зависимость.

Представьте себе этот псевдокод ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass напрямую зависит как от класса Service, так и от класса ServiceLocator. Это нужно обоим, если вы хотите использовать его в другом приложении. Теперь представьте это ...

public class MyClass
{
  public IService myService;
}

Теперь MyClass использует единый интерфейс - интерфейс IService. Мы позволили бы контейнеру IoC фактически установить значение этой переменной.

Так что теперь MyClass можно легко использовать в других проектах, не добавляя зависимости двух других классов вместе с ним.

Более того, вам не нужно перетаскивать зависимости MyService, а также зависимости этих зависимостей, и ... ну, вы поняли идею.

Марк Хьюз
источник
2
Этот ответ на самом деле не о DIP, а о другом. Два фрагмента кода могут опираться на какой-то абстрактный интерфейс, а не друг на друга, и все же не следовать принципу инверсии зависимости.
Rogério
1

Если мы можем принять это как данность, что сотруднику «высокого уровня» в корпорации платят за выполнение их планов, и что эти планы реализуются путем совокупного выполнения планов многих сотрудников «низкого уровня», то мы могли бы сказать, Как правило, это ужасный план, если описание плана сотрудника высокого уровня каким-либо образом связано с конкретным планом любого сотрудника более низкого уровня.

Если у высокопоставленного руководителя есть план «улучшить время доставки» и он указывает, что сотрудник на линии отгрузки должен пить кофе и делать растяжки каждое утро, тогда этот план тесно связан и имеет низкую сплоченность. Но если в плане не упоминается какой-либо конкретный сотрудник, а на самом деле просто требуется «субъект, который может выполнять работу, готов к работе», то план слабо связан и более сплочен: планы не перекрываются и могут быть легко заменены , Подрядчики или роботы могут легко заменить сотрудников, и план высокого уровня остается неизменным.

«Высокий уровень» в принципе инверсии зависимостей означает «более важный».

Майк
источник
1
Это удивительно хорошая аналогия. Как и в случае с DIC, компоненты высокого уровня все еще зависят во время выполнения от компонентов низкого уровня, по этой аналогии выполнение планов высокого уровня зависит от планов низкого уровня. Но также, как и в случае с DIC, планы низкого уровня зависят во время компиляции от планов высокого уровня, в вашей аналогии дело в том, что формирование планов низкого уровня зависит от планов высокого уровня.
Марк Амери
0

Я вижу, хорошее объяснение было дано в ответах выше. Однако я хочу дать несколько простых объяснений на простом примере.

Принцип инверсии зависимостей позволяет программисту удалять жестко закодированные зависимости, чтобы приложение стало слабо связанным и расширяемым.

Как этого добиться: через абстракцию

Без инверсии зависимости:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

В приведенном выше фрагменте кода адресный объект жестко запрограммирован. Вместо этого, если мы можем использовать инверсию зависимостей и внедрить объект адреса, передавая метод конструктора или метода установки. Посмотрим.

С инверсией зависимостей:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}
Суманта Варада
источник
-1

Инверсия зависимостей: зависит от абстракций, а не от конкреций.

Инверсия управления: Main против Abstraction, и как Main является связующим звеном систем.

DIP и IoC

Вот несколько хороших постов, рассказывающих об этом:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/

Даниэль Андрес Пелаес Лопес
источник
-1

Допустим, у нас есть два класса: Engineerи Programmer:

Class Engineer зависит от класса Programmer, как показано ниже:

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

В этом примере класс Engineerимеет зависимость от нашего Programmerкласса. Что будет, если мне нужно поменять Programmer?

Очевидно, мне нужно изменить Engineerтоже. (Вау, в этот момент OCPтоже нарушается)

Тогда, что мы должны навести порядок? Ответ на самом деле абстракция. По абстракции мы можем удалить зависимость между этими двумя классами. Например, я могу создать Interfaceкласс для программиста, и теперь каждый класс, который хочет использовать его, Programmerдолжен использовать егоInterface Затем, изменяя класс Programmer, нам не нужно менять какие-либо классы, которые его использовали, из-за абстракции, которую мы используемый.

Примечание: DependencyInjectionможет помочь нам сделать DIPи SRPтоже.

Ehsan
источник
-1

Добавляя к общему количеству хороших ответов, я хотел бы добавить свой небольшой образец, чтобы продемонстрировать хорошую или плохую практику. И да, я не тот, кто бросает камни!

Скажем, вам нужна небольшая программа для преобразования строки в формат base64 через консольный ввод-вывод. Вот наивный подход:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

DIP в основном говорит, что высокоуровневые компоненты не должны зависеть от низкоуровневой реализации, где «уровень» - это расстояние от ввода-вывода согласно Роберту К. Мартину («Чистая архитектура»). Но как вы выходите из этого положения? Просто сделав центральный кодер зависимым только от интерфейсов, не беспокоясь о том, как они реализованы:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Обратите внимание, что вам не нужно прикасаться GoodEncoder, чтобы изменить режим ввода / вывода - этот класс доволен интерфейсами ввода / вывода, которые он знает; любая реализация низкого уровня IReadableи IWriteableникогда не будет беспокоить это.

Джон Молчание
источник
Я не думаю, что это инверсия зависимости. В конце концов, вы не обращали никакой зависимости здесь; ваш читатель и писатель не зависят от GoodEncoderвашего второго примера. Чтобы создать пример DIP, вам нужно ввести понятие о том, что «владеет» интерфейсами, которые вы извлекли здесь - и, в частности, поместить их в тот же пакет, что и GoodEncoder, пока их реализации остаются вне.
Марк Амери
-2

Принцип инверсии зависимости (DIP) гласит, что

i) Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.

ii) Абстракции никогда не должны зависеть от деталей. Детали должны зависеть от абстракций.

Пример:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Примечание. Класс должен зависеть от абстракций, таких как интерфейс или абстрактные классы, а не от конкретных деталей (реализация интерфейса).

Rejwanul Reja
источник