Что такое принцип обращения зависимостей и почему он важен? - PullRequest
165 голосов
/ 15 сентября 2008

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

Ответы [ 13 ]

134 голосов
/ 11 июля 2009

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

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

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

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

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

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

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

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

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

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

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

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

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

102 голосов
/ 15 сентября 2008

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

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

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

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

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

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

11 голосов
/ 10 сентября 2016

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

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

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

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

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

9 голосов
/ 12 февраля 2016

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

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

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

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

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

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

// 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;
}

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

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

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

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

// Domain.dll
public interface IProductRepository;

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

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

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

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

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

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

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

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

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

Архитектура

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

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

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

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

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

9 голосов
/ 31 августа 2009

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

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

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

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

8 голосов
/ 09 июля 2015

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

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

5 голосов
/ 17 мая 2016

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

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

Т.е. вместо реализации вашего класса 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 с поддельными данными и передавать их. Нет необходимости в макетах или сложных тестовых лесах.
5 голосов
/ 20 января 2009

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

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

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

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

3 голосов
/ 15 сентября 2008

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

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

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

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

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

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

  • Вы не зависите от центрального рамки, так что это можно изменить, если желательно.
  • Поскольку объекты созданы путем инъекции, предпочтительно с использованием интерфейсы, легко создать блок тесты, которые заменяют зависимости фиктивные версии.
  • Отключение кода.
1 голос
/ 15 сентября 2008

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

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

Чаще всего это достигается путем использования контейнера инверсии управления (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 и зависимости этих зависимостей, и ... ну, вы поняли.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...