Что такое внедрение зависимостей?


3077

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

Что такое внедрение зависимостей и когда / почему его следует или не следует использовать?


Смотрите мою дискуссию о внедрении зависимости здесь .
Кевин С.

33
Я согласен с комментариями относительно ссылок. Я могу понять, что вы можете ссылаться на кого-то еще. Но хотя бы добавьте, почему вы их связываете и что делает эту ссылку лучше, чем другие ссылки, которые я мог бы получить с помощью Google
Christian Payne

@AR: Технически, Dependency Injection не является особой формой IoC. Скорее IoC является одним из методов, который используется для обеспечения внедрения зависимости. Другие методы могут использоваться для обеспечения внедрения зависимостей (хотя IoC является единственным широко используемым), и IoC также используется для решения многих других проблем.
Шон Рейли

Одно из самых хороших объяснений, которые я когда-либо читал о DI, - от Guice Google (произносится как сок) http://code.google.com/p/google-guice/wiki/Motivation?tm=6
Raj

136
Что касается ссылок, помните, что они часто исчезают так или иначе. В SO-ответах растет количество мертвых ссылок. Таким образом, независимо от того, насколько хороша связанная статья, это бесполезно, если вы не можете ее найти.
DOK

Ответы:


1931

Внедрение зависимости - это передача зависимости другим объектам или инфраструктуре (инжектор зависимости).

Внедрение зависимостей облегчает тестирование. Внедрение может быть сделано через конструктор .

SomeClass() имеет конструктор следующим образом:

public SomeClass() {
    myObject = Factory.getObject();
}

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

Альтернативное решение :

  • Передача myObjectв качестве аргумента конструктору
public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

myObject может быть передан напрямую, что облегчает тестирование.

  • Одна из распространенных альтернатив - определение конструктора , который ничего не делает . Внедрение зависимости может быть сделано через сеттеры. (h / t @MikeVella).
  • Мартин Фаулер описывает третий вариант (h / t @MarcDix), где классы явно реализуют интерфейс для зависимостей, которые программисты хотят внедрить .

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

В 2013 году, когда я написал этот ответ, это была основная тема в блоге Google Testing . Это остается для меня самым большим преимуществом, так как программистам не всегда нужна дополнительная гибкость в их дизайне времени выполнения (например, для поиска служб или подобных шаблонов). Программисты часто должны изолировать классы во время тестирования.


25
Признавая, что ссылка Бена Хоффштейна на статью Мартина Фаулера необходима, поскольку она указывает на обязательное прочтение этой темы, я принимаю ответ wds, потому что он фактически отвечает на вопрос здесь о SO.
АР.

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

13
Вы говорите, что зависимость передается "конструктору", но, насколько я понимаю, это не совсем верно. Это все еще внедрение зависимости, если зависимость установлена ​​как свойство после того, как объект был создан, правильно?
Майк Велла

1
@MikeVella Да, это правильно. В большинстве случаев это не имеет большого значения, хотя свойства обычно немного более гибкие. Я немного отредактирую текст, чтобы указать на это.
wds

2
Один из лучших ответов, которые я нашел до сих пор, поэтому я действительно заинтересован в его улучшении. В нем отсутствует описание третьей формы внедрения зависимостей: внедрение интерфейса .
Марк Дикс,

2351

Лучшее определение, которое я нашел до сих пор, - это определение Джеймса Шора :

«Внедрение зависимостей» - это понятие за 5 центов стоимостью 25 долларов. [...] Внедрение зависимости означает предоставление объекту его переменных экземпляра. [...].

Есть статья Мартина Фаулера, которая также может оказаться полезной.

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

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


35
Мне нравится объяснение статьи Джеймса, особенно конец: «Тем не менее, вы должны удивляться любому подходу, который использует три концепции (« TripPlanner »,« CabAgency »и« AirlineAgency »), превращает их в девять с лишним классов, а затем добавляет десятки строк связующего кода и конфигурационного XML, прежде чем будет написана единственная строка логики приложения ». Это то, что я видел очень часто (к сожалению) - что внедрение зависимостей (что само по себе хорошо, как он объяснил) неправильно используется для усложнения вещей, которые можно было бы сделать проще - в конечном итоге написать «поддерживающий» код ...
Мэтт

2
Re: «Создание и передача объектов (зависимостей) в явном виде - такая же хорошая инъекция, как инъекция фреймворком». Итак, почему люди сделали рамки, делающие это?
dzieciou

13
По той же причине, по которой каждая инфраструктура написана (или, по крайней мере, должна быть написана): потому что существует много повторяющихся / шаблонных кодов, которые необходимо написать, как только вы достигнете определенной сложности. Проблема в том, что люди часто добиваются рамок, даже если они не нужны строго.
Тьяго Аррай

14
Срок в $ 25 за концепцию в 5 центов мертв. Вот хорошая статья, которая мне помогла: codeproject.com/Articles/615139/…
Кристина,

@Matt файлы конфигурации - это просто «Отложить решение», доведенное до крайности - «Отложить решение до фактического времени выполнения». Dagger и особенно Dagger, по моему мнению, нашли приятное место: «Отложите решение до времени сборки приложения».
Турбьерн Равн Андерсен

645

Я нашел этот забавный пример с точки зрения слабой связи :

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

Например, рассмотрим Carобъект.

А Carзависит от колес, двигателя, топлива, аккумулятора и т. Д., Чтобы работать. Традиционно мы определяем марку таких зависимых объектов вместе с определением Carобъекта.

Без внедрения зависимостей (DI):

class Car{
  private Wheel wh = new NepaliRubberWheel();
  private Battery bt = new ExcideBattery();

  //The rest
}

Здесь Carобъект отвечает за создание зависимых объектов.

Что если мы захотим изменить тип зависимого объекта, скажем, Wheelпосле начальных NepaliRubberWheel()проколов? Нам нужно воссоздать объект Car с его новой зависимостью, скажем ChineseRubberWheel(), но это Carможет сделать только производитель.

Тогда что Dependency Injectionнам делать ...?

При использовании внедрения зависимостей объектам присваиваются свои зависимости во время выполнения, а не во время компиляции (время изготовления автомобиля) . Так что теперь мы можем изменить, Wheelкогда захотим. Здесь, dependency( wheel) может быть введен во Carвремя выполнения.

После использования зависимости зависимости:

Здесь мы инъекционные в зависимость (колесо и аккумулятор) во время выполнения. Отсюда и термин: введение зависимости.

class Car{
  private Wheel wh; // Inject an Instance of Wheel (dependency of car) at runtime
  private Battery bt; // Inject an Instance of Battery (dependency of car) at runtime
  Car(Wheel wh,Battery bt) {
      this.wh = wh;
      this.bt = bt;
  }
  //Or we can have setters
  void setWheel(Wheel wh) {
      this.wh = wh;
  }
}

Источник: Понимание внедрения зависимости


20
Насколько я понимаю, вместо того, чтобы создавать новый объект как часть другого объекта, мы можем внедрить указанный объект, когда и если это необходимо, таким образом удаляя зависимость первого объекта от него. Это правильно?
JeliBeanMachine

Я описал это на примере кофейни здесь: digigene.com/design-patterns/dependency-injection-coffeeshop
Али Нем

11
Очень нравится эта аналогия, потому что это простой английский, использующий простую аналогию. Скажем, я Toyota, уже потратившая слишком много денег и сил на создание автомобиля от проектирования до съезда с конвейера, если есть авторитетные производители шин, почему я должен начать с нуля, чтобы создать подразделение по производству шин, то newесть шина? Я не. Все, что мне нужно сделать, это купить (ввести через Param) от них, установить и вау-лах! Итак, возвращаясь к программированию, скажем, проекту C # нужно использовать существующую библиотеку / класс, есть два способа запустить / отладить, 1-добавить ссылку на весь проект этого
Jeb50

(con't), .. внешняя библиотека / класс, или 2 - добавьте его из DLL. Если только мы не увидим, что находится внутри этого внешнего класса, добавить его в качестве DLL проще. Таким образом, вариант 1 для newнего, вариант 2 передать его в качестве параметра. Может быть не точным, но просто глупо легко понять.
Jeb50

1
@JeliBeanMachine (извините за крайне поздний ответ на комментарий ..) дело не в том, что мы удаляем зависимость первого объекта от объекта колеса или батареи, а в том, что мы передаем ему зависимость, чтобы мы могли изменить экземпляр или реализацию зависимость. До: Автомобиль имеет жесткую зависимость от NepaliRubberWheel. После: Автомобиль имеет введенную зависимость от экземпляра колеса.
Микаэль Олсон

263

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

Например, рассмотрим эти предложения:

public class PersonService {
  public void addManager( Person employee, Person newManager ) { ... }
  public void removeManager( Person employee, Person oldManager ) { ... }
  public Group getGroupByManager( Person manager ) { ... }
}

public class GroupMembershipService() {
  public void addPersonToGroup( Person person, Group group ) { ... }
  public void removePersonFromGroup( Person person, Group group ) { ... }
} 

В этом примере реализации PersonService::addManagerи PersonService::removeManagerпотребуется экземпляр для GroupMembershipServiceтого, чтобы выполнить свою работу. Без внедрения зависимостей традиционный способ сделать это - создать новый экземпляр GroupMembershipServiceв конструкторе PersonServiceи использовать этот атрибут экземпляра в обеих функциях. Однако, если конструкторGroupMembershipService имеет несколько вещей, которые ему требуются, или, что еще хуже, есть некоторые «установщики» инициализации, которые необходимо вызывать GroupMembershipService, код растет довольно быстро, и PersonServiceтеперь это зависит не только от, GroupMembershipServiceно и от всего остального, что GroupMembershipServiceзависит от. Кроме того, связь с GroupMembershipServiceжестко закодирована в, PersonServiceчто означает, что вы не можете "пустить в тупик"GroupMembershipService для тестирования или для использования шаблона стратегии в различных частях вашего приложения.

С внедрением Dependency Injection вместо создания экземпляра GroupMembershipServiceвнутри вашего PersonServiceвы бы либо передали его PersonServiceконструктору, либо добавили свойство (getter и setter), чтобы установить его локальный экземпляр. Это означает, что вам PersonServiceбольше не нужно беспокоиться о том, как создать GroupMembershipService, он просто принимает те, которые он получил, и работает с ними. Это также означает, что все, что является подклассом GroupMembershipServiceили реализует GroupMembershipServiceинтерфейс, может быть « внедрено » в PersonService, и PersonServiceне нужно знать об изменениях.


29
Было бы здорово, если бы вы могли привести тот же пример кода ПОСЛЕ использования DI
CodyBugstein

170

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

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

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


18
+1 «объекты меняются чаще, чем код, который их использует». Чтобы обобщить, добавьте косвенное направление в точках потока. В зависимости от точки потока косвенные зависимости называются разными именами !!
Четан

139

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

public class Car
{
    public Car()
    {
        GasEngine engine = new GasEngine();
        engine.Start();
    }
}

public class GasEngine
{
    public void Start()
    {
        Console.WriteLine("I use gas as my fuel!");
    }
}

И чтобы создать экземпляр класса Car, мы будем использовать следующий код:

Car car = new Car();

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

Другими словами, при таком подходе наш класс автомобилей высокого уровня зависит от класса GasEngine более низкого уровня, который нарушает принцип инверсии зависимости (DIP) от SOLID. DIP предполагает, что мы должны зависеть от абстракций, а не от конкретных классов. Таким образом, чтобы удовлетворить это, мы вводим интерфейс IEngine и переписываем код, как показано ниже:

    public interface IEngine
    {
        void Start();
    }

    public class GasEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I use gas as my fuel!");
        }
    }

    public class ElectricityEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I am electrocar");
        }
    }

    public class Car
    {
        private readonly IEngine _engine;
        public Car(IEngine engine)
        {
            _engine = engine;
        }

        public void Run()
        {
            _engine.Start();
        }
    }

Теперь наш класс Car зависит только от интерфейса IEngine, а не от конкретной реализации движка. Теперь единственная хитрость заключается в том, как создать экземпляр Car и присвоить ему конкретный класс Engine, такой как GasEngine или ElectricityEngine. Вот где вводится зависимость .

   Car gasCar = new Car(new GasEngine());
   gasCar.Run();
   Car electroCar = new Car(new ElectricityEngine());
   electroCar.Run();

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

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

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

Также, когда у нас много зависимостей, очень хорошая практика - использовать контейнеры Inversion of Control (IoC), которые мы можем сказать, какие интерфейсы должны быть отображены, какие конкретные реализации для всех наших зависимостей, и мы можем разрешить им разрешить эти зависимости для нас при создании наш объект. Например, мы могли бы указать в сопоставлении для контейнера IoC, что зависимость IEngine должна быть сопоставлена ​​с классом GasEngine, и когда мы запрашиваем у контейнера IoC экземпляр нашего класса Car , он автоматически создаст наш класс Car с помощью GasEngine. зависимостью прошло в.

ОБНОВЛЕНИЕ: Недавно посмотрел курс об EF Core от Джули Лерман, а также понравилось ее краткое определение о DI.

Внедрение зависимостей - это шаблон, позволяющий вашему приложению на лету внедрять объекты в классы, которые в них нуждаются, не заставляя эти классы отвечать за эти объекты. Это позволяет вашему коду быть более свободным, а Entity Framework Core подключается к той же системе сервисов.


2
просто из любопытства, как это отличается от модели стратегии? Этот шаблон инкапсулирует алгоритмы и делает их взаимозаменяемыми. Такое ощущение, что внедрение зависимости и шаблоны стратегии очень похожи.
эликсир

110

Давайте представим, что вы хотите пойти на рыбалку:

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

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


59
С другой стороны, представьте, что вы нанимаете сантехника, чтобы переделать ванную комнату, и он говорит: «Отлично, вот список инструментов и материалов, которые мне нужно, чтобы вы достали для меня». Разве это не должно быть работой сантехника?
ОАО

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

22
@JoshCaswell Нет, это будет работа emplyer водопроводчика. Как клиент, вам нужно сделать сантехнику. Для этого вам нужен сантехник. Сантехнику нужны инструменты. Чтобы получить это, это оборудовано водопроводной компанией. Как клиент, вы не хотите точно знать, что делает или нуждается сантехник. Как сантехник вы знаете, что вам нужно, но вы просто хотите делать свою работу, а не получать все. Как работодатель сантехников, вы несете ответственность за то, чтобы снабдить своих сантехников необходимым, прежде чем отправлять их в дома людей.
сара

@ Кай, я понимаю твою точку зрения. В программном обеспечении речь идет о фабрике, верно? Но DI также обычно означает, что класс не использует фабрику, поскольку она все еще не введена. Вам, клиенту, нужно будет связаться с работодателем (фабрикой), чтобы дать вам инструменты, чтобы вы могли перейти к сантехнику. Разве это не так, как это будет работать в программе? Таким образом, в то время как клиент (вызывающий класс / функцию / что угодно) не должен закупать инструменты, он все равно должен быть посредником, гарантирующим, что он доберется до сантехника (введенный класс) от работодателя (фабрика).
KingOfAllTrades

1
@KingOfAllTrades: Конечно, в какой-то момент вам нужно, чтобы кто-то нанимал сантехников и оборудовал их, или у вас нет сантехников. Но у вас нет клиента, делающего это. Заказчик просто просит сантехника, и тот получает уже то, что ему нужно для выполнения своей работы. С DI у вас все еще есть некоторый код для выполнения зависимостей. Но вы отделяете его от кода, который действительно работает. Если вы возьмете его в полную силу, ваши объекты просто сообщат свои зависимости, и построение графов объектов будет происходить снаружи, часто в коде инициализации.
cHao

102

Это самое простое объяснение контейнера ввода зависимостей и зависимостей, которое я когда-либо видел:

Без внедрения зависимостей

  • Приложению требуется Foo (например, контроллер), поэтому:
  • Приложение создает Foo
  • Приложение вызывает Foo
    • Foo нуждается в Bar (например, услуга), поэтому:
    • Фу создает бар
    • Фу называет бар
      • Бар нужен Bim (сервис, хранилище,…), поэтому:
      • Бар создает Бим
      • Бар что-то делает

С инъекцией зависимостей

  • Приложению нужен Foo, которому нужен Bar, которому нужен Bim, так:
  • Приложение создает Bim
  • Приложение создает бар и дает ему Бим
  • Приложение создает Foo и дает ему Бар
  • Приложение вызывает Foo
    • Фу называет бар
      • Бар что-то делает

Использование контейнера внедрения зависимостей

  • Приложение нуждается в Foo так:
  • Приложение получает Foo из контейнера, поэтому:
    • Контейнер создает Bim
    • Контейнер создает Бар и дает ему Бим
    • Контейнер создает Foo и дает ему Bar
  • Приложение вызывает Foo
    • Фу называет бар
      • Бар что-то делает

Dependency Injection и Dependency Injection Контейнеры разные вещи:

  • Dependency Injection - это метод для написания лучшего кода
  • DI-контейнер - это инструмент, помогающий внедрять зависимости

Вам не нужен контейнер для внедрения зависимостей. Однако контейнер может помочь вам.



55

Разве «внедрение зависимостей» не означает просто использование параметризованных конструкторов и открытых сеттеров?

Статья Джеймса Шора показывает следующие примеры для сравнения .

Конструктор без внедрения зависимостей:

public class Example { 
  private DatabaseThingie myDatabase; 

  public Example() { 
    myDatabase = new DatabaseThingie(); 
  } 

  public void doStuff() { 
    ... 
    myDatabase.getData(); 
    ... 
  } 
} 

Конструктор с внедрением зависимостей:

public class Example { 
  private DatabaseThingie myDatabase; 

  public Example(DatabaseThingie useThisDatabaseInstead) { 
    myDatabase = useThisDatabaseInstead; 
  }

  public void doStuff() { 
    ... 
    myDatabase.getData(); 
    ... 
  } 
}

Конечно, в версии DI вы не захотите инициализировать объект myDatabase в конструкторе без аргументов? Кажется, нет никакого смысла, и он будет вызывать исключение, если вы попытаетесь вызвать DoStuff без вызова перегруженного конструктора?
Мэтт Вилко

Только если new DatabaseThingie()не генерирует действительный экземпляр myDatabase.
JaneGoodall

40

Сделать концепцию внедрения зависимостей простой для понимания. Давайте рассмотрим пример кнопки переключения для включения / выключения лампы.

Без внедрения зависимостей

Switch должен знать заранее, к какой лампочке я подключен (жестко запрограммированная зависимость). Так,

Switch -> PermanentBulb // переключатель напрямую подключен к постоянной лампе, тестирование невозможно

Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}

С инъекцией зависимостей

Коммутатор знает только, что мне нужно включить / выключить любую лампочку, которую мне передают. Так,

Переключатель -> Bulb1 ИЛИ Bulb2 ИЛИ NightBulb (введенная зависимость)

Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}

Модификация Джеймса для Switch и Bulb:

public class SwitchTest { 
  TestToggleBulb() { 
    MockBulb mockbulb = new MockBulb(); 

    // MockBulb is a subclass of Bulb, so we can 
    // "inject" it here: 
    Switch switch = new Switch(mockBulb); 

    switch.ToggleBulb(); 
    mockBulb.AssertToggleWasCalled(); 
  } 
}

public class Switch { 
  private Bulb myBulb; 

  public Switch() { 
    myBulb = new Bulb(); 
  } 

  public Switch(Bulb useThisBulbInstead) { 
    myBulb = useThisBulbInstead; 
  } 

  public void ToggleBulb() { 
    ... 
    myBulb.Toggle(); 
    ... 
  } 
}`

36

Что такое инъекция зависимостей (DI)?

Как уже говорили другие, Dependency Injection (DI) устраняет ответственность за непосредственное создание и управление продолжительностью жизни других экземпляров объекта, от которых зависит наш класс интересов (потребительский класс) (в смысле UML ). Эти экземпляры вместо этого передаются в наш потребительский класс, как правило, в качестве параметров конструктора или через установщики свойств (управление экземпляром объекта зависимости и передачей в потребительский класс обычно выполняется с помощью Inversion of Control (IoC) контейнером , но это другая тема) ,

DI, DIP и SOLID

В частности, в парадигме SOLID принципов Роберта С. Мартина объектно-ориентированного проектирования , DIявляется одной из возможных реализаций принципа инверсии зависимости (DIP) . DIP является Dв SOLIDмантре - другие реализации DIP включают Service Locator, и паттерны Plugin.

Целью DIP является разделение плотно, конкретные зависимости между классами, и вместо того , чтобы ослабить муфту с помощью абстракции, которые могут быть достигнуты с помощью interface, abstract classилиpure virtual class , в зависимости от используемого языка и подхода.

Без DIP наш код (я назвал это «потребляющий класс») напрямую связан с конкретной зависимостью и также часто обременен обязанностью знать, как получить и управлять экземпляром этой зависимости, то есть концептуально:

"I need to create/use a Foo and invoke method `GetBar()`"

Принимая во внимание, что после применения DIP требование ослабляется, и Fooустраняется проблема получения и управления сроком службы зависимости:

"I need to invoke something which offers `GetBar()`"

Зачем использовать DIP (и DI)?

Разделение зависимостей между классами таким способом позволяет легко заменить эти классы зависимостей другими реализациями, которые также выполняют предварительные условия абстракции (например, зависимость может быть переключена с другой реализацией того же интерфейса). Кроме того, как уже упоминалось, возможно , , наиболее распространенной причиной для классов разъединить с помощью DIP, чтобы позволить потребляя класс для тестирования в изоляции, так как эти же зависимостей теперь могут быть загасил и / или издевались.

Одним из следствий DI является то, что управление временем жизни экземпляров объекта зависимости больше не контролируется потребляющим классом, так как объект зависимости теперь передается в потребляющий класс (через внедрение конструктора или сеттера).

Это можно посмотреть по-разному:

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

Когда использовать DI?

  • Там, где, вероятно, возникнет необходимость заменить зависимость эквивалентной реализацией,
  • В любое время, когда вам нужно будет тестировать методы класса изолированно от его зависимостей,
  • Там, где неопределенность продолжительности жизни зависимости может потребовать экспериментов (например, «Эй, MyDepClassэто потокобезопасность?

пример

Вот простая реализация C #. Учитывая ниже класс потребления:

public class MyLogger
{
   public void LogRecord(string somethingToLog)
   {
      Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
   }
}

Несмотря на то, что он выглядит безобидным, он имеет две staticзависимости от двух других классов System.DateTimeи System.Console, который не только ограничивает параметры вывода журналов (вход в консоль будет бесполезным, если никто не наблюдает), но, что еще хуже, автоматическое тестирование с учетом зависимости от этого затруднительно недетерминированные системные часы.

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

public interface IClock
{
    DateTime Now { get; }
}

Мы также можем ослабить зависимость от Consoleабстракции, такой как TextWriter. Внедрение зависимостей обычно реализуется либо как constructorвнедрение (передача абстракции зависимости в качестве параметра конструктору потребляющего класса), либо Setter Injection(передача зависимости через setXyz()установщик или свойство .Net с {set;}заданным значением). Внедрение в конструктор является предпочтительным, поскольку это гарантирует, что класс будет в правильном состоянии после создания, и позволяет полям внутренней зависимости помечаться как readonly(C #) или final(Java). Таким образом, используя инъекцию конструктора в приведенном выше примере, мы получаем:

public class MyLogger : ILogger // Others will depend on our logger.
{
    private readonly TextWriter _output;
    private readonly IClock _clock;

    // Dependencies are injected through the constructor
    public MyLogger(TextWriter stream, IClock clock)
    {
        _output = stream;
        _clock = clock;
    }

    public void LogRecord(string somethingToLog)
    {
        // We can now use our dependencies through the abstraction 
        // and without knowledge of the lifespans of the dependencies
        _output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
    }
}

(Необходимо предоставить конкретную Clockинформацию, к которой, конечно, можно вернуться DateTime.Now, и две зависимости должны быть предоставлены контейнером IoC посредством внедрения конструктора)

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

[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
    // Arrange
    var mockClock = new Mock<IClock>();
    mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
    var fakeConsole = new StringWriter();

    // Act
    new MyLogger(fakeConsole, mockClock.Object)
        .LogRecord("Foo");

    // Assert
    Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}

Следующие шаги

Внедрение зависимостей неизменно связано с контейнером инверсии (IoC) , чтобы внедрить (предоставить) конкретные экземпляры зависимостей и управлять экземплярами продолжительности жизни. В процессе конфигурирования / начальной загрузки IoCконтейнеры позволяют определять следующее:

  • отображение между каждой абстракцией и сконфигурированной конкретной реализацией (например, «всякий раз, когда потребитель запрашивает IBar, возвращает ConcreteBarэкземпляр» )
  • Политики могут быть настроены для управления продолжительностью жизни каждой зависимости, например, для создания нового объекта для каждого экземпляра-потребителя, для совместного использования экземпляра одноэлементной зависимости для всех потребителей, для совместного использования одного и того же экземпляра зависимости только в одном потоке и т. д.
  • В .Net контейнеры IoC знают о таких протоколах, как IDisposableи будут нести ответственность за Disposingзависимости в соответствии с настроенным управлением продолжительностью жизни.

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

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

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

Но преимуществ много, особенно в том, что касается возможности тщательно проверить свой класс интересов.

Примечание . Создание / отображение / проекция (через new ..()) POCO / POJO / Сериализация DTO / Графы сущностей / Анонимные проекции JSON и др., Т.е. классы или записи «Только данные», используемые или возвращаемые из методов, не рассматриваются как зависимости (в Смысл UML) и не подлежит DI. Использование newдля проецирования это просто отлично.


1
Проблема в DIP! = DI. DIP посвящен отделению абстракции от реализации: A. Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций. Б. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. DI - это способ отделить создание объекта от использования объекта.
Рикардо Ривалдо

Да, различие четко указано в моем параграфе 2, «DI одна из возможных реализаций DIP» , в парадигме SOLID дядюшки Боба. Я также ясно дал понять это в предыдущем посте.
StuartLC

25

Смысл Dependency Injection (DI) в том, чтобы поддерживать исходный код приложения в чистоте и стабильности :

  • очистить от кода инициализации зависимости
  • стабильный независимо от используемой зависимости

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

Конкретной областью DI является делегирование конфигурации и инициализации зависимостей.

Пример: DI со сценарием оболочки

Если вы время от времени работаете за пределами Java, вспомните, как sourceэто часто используется во многих языках сценариев (Shell, Tcl и т. Д. Или даже importв Python, неправильно используемых для этой цели).

Рассмотрим простой dependent.shскрипт:

#!/bin/sh
# Dependent
touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

Сценарий является зависимым: он не будет успешно выполнен самостоятельно ( archive_filesне определен).

Вы определяете archive_filesв archive_files_zip.shскрипте реализации (используя zipв этом случае):

#!/bin/sh
# Dependency
function archive_files {
    zip files.zip "$@"
}

Вместо source-ing сценария реализации непосредственно в зависимом, вы используете injector.sh«контейнер», который оборачивает оба «компонента»:

#!/bin/sh 
# Injector
source ./archive_files_zip.sh
source ./dependent.sh

archive_files Зависимость только что была введена в зависимый сценарий.

Вы могли бы ввести зависимость, которая реализует archive_filesиспользование tarили xz.

Пример: удаление DI

Если dependent.shскрипт использует зависимости напрямую, этот подход будет называться поиском зависимостей (что противоположно внедрению зависимостей ):

#!/bin/sh
# Dependent

# dependency look-up
source ./archive_files_zip.sh

touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

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

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

Последние слова

DI не так сильно подчеркивается и популяризируется, как в Java-фреймворках.

Но это общий подход для разделения проблем:

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

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


Я бы добавил возможность завершать определенный класс (тестирование) без необходимости завершения его зависимостей, как цель для DI.
Дэвид

22

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

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

Мы можем видеть широкое применение этого шаблона в нашей повседневной жизни. Некоторые из примеров: магнитофон, VCD, CD-привод и т. Д.

Катушечный портативный магнитофон, середина 20-го века.

Это изображение представляет собой портативный магнитофон Reel-to-reel, середина 20-го века. Источник .

Основным назначением магнитофона является запись или воспроизведение звука.

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

  1. мы можем разместить катушку внутри машины
  2. мы можем предоставить крючок для катушки, где он может быть размещен.

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

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

Основные преимущества мы достигли с помощью внедрения зависимостей.

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

В наши дни эта концепция лежит в основе известных фреймворков в мире программирования. Spring Angular и т. Д. - это хорошо известные программные среды, построенные на основе этой концепции.

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

Пример для внедрения зависимости

Ранее мы пишем такой код

Public MyClass{
 DependentClass dependentObject
 /*
  At somewhere in our code we need to instantiate 
  the object with new operator  inorder to use it or perform some method.
  */ 
  dependentObject= new DependentClass();
  dependentObject.someMethod();
}

С внедрением Dependency инжектор зависимостей снимает для нас экземпляр

Public MyClass{
 /* Dependency injector will instantiate object*/
 DependentClass dependentObject

 /*
  At somewhere in our code we perform some method. 
  The process of  instantiation will be handled by the dependency injector
 */ 

  dependentObject.someMethod();
}

Вы также можете прочитать

Разница между инверсией контроля и инъекцией зависимостей


17

Что такое инъекция зависимости?

Внедрение зависимостей (DI) означает разделение объектов, которые зависят друг от друга. Скажем, объект A зависит от объекта B, поэтому идея состоит в том, чтобы отделить эти объекты друг от друга. Нам не нужно жестко кодировать объект с использованием нового ключевого слова, а делиться зависимостями с объектами во время выполнения, несмотря на время компиляции. Если мы говорим о

Как работает Dependency Injection весной:

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

Инверсия Контроля (МОК)

МОК является общей концепцией, и ее можно выразить многими различными способами, а внедрение зависимости является одним из конкретных примеров МОК.

Два типа внедрения зависимостей:

  1. Конструктор Инъекция
  2. Сеттер Инъекция

1. Конструкторское внедрение зависимостей:

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

public class Triangle {

private String type;

public String getType(){
    return type;
 }

public Triangle(String type){   //constructor injection
    this.type=type;
 }
}
<bean id=triangle" class ="com.test.dependencyInjection.Triangle">
        <constructor-arg value="20"/>
  </bean>

2. Установка зависимости на основе установки:

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

public class Triangle{

 private String type;

 public String getType(){
    return type;
  }
 public void setType(String type){          //setter injection
    this.type = type;
  }
 }

<!-- setter injection -->
 <bean id="triangle" class="com.test.dependencyInjection.Triangle">
        <property name="type" value="equivialteral"/>

ПРИМЕЧАНИЕ. Хорошее практическое правило - использовать аргументы конструктора для обязательных зависимостей и методы установки для необязательных зависимостей. Обратите внимание, что если мы используем аннотацию на основе, то аннотацию @Required для установщика можно использовать для создания сеттеров в качестве необходимых зависимостей.


15

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

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

DI приближает вас к принципу единой ответственности (SR), например surgeon who can concentrate on surgery.

Когда использовать DI: Я бы рекомендовал использовать DI практически во всех производственных проектах (малых / больших), особенно в постоянно меняющихся бизнес-средах :)

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


@WindRider Спасибо. Я не могу согласиться больше. Человеческая жизнь и человеческое тело - великолепные примеры превосходного дизайна. Позвоночник - отличный пример ESB:) ...
Анвар Хусейн

15

Например, у нас есть 2 класса Clientи Service. Clientбуду использоватьService

public class Service {
    public void doSomeThingInService() {
        // ...
    }
}

Без внедрения зависимостей

Способ 1)

public class Client {
    public void doSomeThingInClient() {
        Service service = new Service();
        service.doSomeThingInService();
    }
}

Способ 2)

public class Client {
    Service service = new Service();
    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

Способ 3)

public class Client {
    Service service;
    public Client() {
        service = new Service();
    }
    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

1) 2) 3) Использование

Client client = new Client();
client.doSomeThingInService();

преимущества

  • просто

Недостатки

  • Тяжело для тестового Clientкласса
  • Когда мы меняем Serviceконструктор, нам нужно изменить код во всем месте создания Serviceобъекта

Использовать инъекцию зависимостей

Способ 1) Конструктор впрыска

public class Client {
    Service service;

    Client(Service service) {
        this.service = service;
    }

    // Example Client has 2 dependency 
    // Client(Service service, IDatabas database) {
    //    this.service = service;
    //    this.database = database;
    // }

    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

С помощью

Client client = new Client(new Service());
// Client client = new Client(new Service(), new SqliteDatabase());
client.doSomeThingInClient();

Способ 2) Сеттер впрыска

public class Client {
    Service service;

    public void setService(Service service) {
        this.service = service;
    }

    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

С помощью

Client client = new Client();
client.setService(new Service());
client.doSomeThingInClient();

Способ 3) Внедрение интерфейса

Проверьте https://en.wikipedia.org/wiki/Dependency_injection

===

Теперь этот код уже соблюдается, Dependency Injectionи он легче для тестового Clientкласса.
Тем не менее, мы все еще используем new Service()много времени, и это не очень хорошо, когда меняем Serviceконструктор. Чтобы предотвратить это, мы можем использовать DI инжектор, как
1) Простое руководствоInjector

public class Injector {
    public static Service provideService(){
        return new Service();
    }

    public static IDatabase provideDatatBase(){
        return new SqliteDatabase();
    }
    public static ObjectA provideObjectA(){
        return new ObjectA(provideService(...));
    }
}

С помощью

Service service = Injector.provideService();

2) Использование библиотеки: для Android dagger2

преимущества

  • Сделать тест проще
  • Когда вы меняете Service, вам нужно только изменить его в классе инжекторов
  • Если вы используете use Constructor Injection, когда вы посмотрите на конструктор of Client, вы увидите, сколько зависимостей Clientкласса

Недостатки

  • Если вы используете use Constructor Injection, Serviceобъект создается при Clientсоздании, иногда мы используем функцию в Clientклассе без использования, Serviceпоэтому созданный объект Serviceтеряется

Определение внедрения зависимости

https://en.wikipedia.org/wiki/Dependency_injection

Зависимость - это объект, который можно использовать ( Service)
. Инъекция - это передача зависимости ( Service) в зависимый объект ( Client), который будет его использовать.


13

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

«Инъекция зависимости» (DI) также известна как «Инверсия контроля» (IoC) и может использоваться в качестве метода для поощрения этой слабой связи.

Существует два основных подхода к реализации DI:

  1. Конструктор инъекций
  2. Сеттер впрыска

Конструктор инъекций

Это техника передачи зависимостей объекта его конструктору.

Обратите внимание, что конструктор принимает интерфейс, а не конкретный объект. Также обратите внимание, что возникает исключение, если параметр orderDao имеет значение null. Это подчеркивает важность получения действительной зависимости. Инжектор конструктора, на мой взгляд, является предпочтительным механизмом предоставления объекту его зависимостей. При вызове объекта для разработчика ясно, какие зависимости необходимо передать объекту «Person» для правильного выполнения.

Сеттер Инъекция

Но рассмотрим следующий пример ... Предположим, у вас есть класс с десятью методами, которые не имеют зависимостей, но вы добавляете новый метод, который имеет зависимость от IDAO. Вы можете изменить конструктор, чтобы использовать конструктор Injection, но это может заставить вас вносить изменения во все вызовы конструктора повсюду. В качестве альтернативы, вы можете просто добавить новый конструктор, который получает зависимость, но тогда как разработчик легко узнает, когда использовать один конструктор над другим. Наконец, если создать зависимость очень дорого, зачем ее создавать и передавать конструктору, если ее можно использовать редко? «Инъекция сеттера» - это еще один метод DI, который можно использовать в таких ситуациях, как эта.

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

  1. Поддержка внедрения зависимостей без необходимости изменения конструктора унаследованного класса.
  2. Позволяет создавать дорогостоящие ресурсы или услуги как можно позже и только при необходимости.

Вот пример того, как будет выглядеть приведенный выше код:

public class Person {
    public Person() {}

    public IDAO Address {
        set { addressdao = value; }
        get {
            if (addressdao == null)
              throw new MemberAccessException("addressdao" +
                             " has not been initialized");
            return addressdao;
        }
    }

    public Address GetAddress() {
       // ... code that uses the addressdao object
       // to fetch address details from the datasource ...
    }

    // Should not be called directly;
    // use the public property instead
    private IDAO addressdao;

3
Я думаю, что ваш первый абзац отклоняется от вопроса и совсем не определяет DI (то есть вы пытаетесь определить SOLID, а не DI). Технически, даже если у вас есть 100 зависимостей, вы все равно можете использовать внедрение зависимостей. Точно так же возможно ввести конкретные зависимости - это все еще внедрение зависимости.
Джей Салливан

10

Я думаю, так как все написали для DI, позвольте мне задать несколько вопросов ..

  1. Если у вас есть конфигурация DI, в которой все фактические реализации (не интерфейсы), которые собираются внедрить в класс (например, для служб контроллера), почему это не какое-то жесткое кодирование?
  2. Что если я захочу изменить объект во время выполнения? Например, мой конфиг уже говорит, когда я создаю экземпляр MyController, введите для FileLogger как ILogger. Но я мог бы хотеть ввести DatabaseLogger.
  3. Каждый раз, когда я хочу изменить объекты, которые нужны моему AClass, мне нужно теперь взглянуть на два места - сам класс и файл конфигурации. Как это облегчает жизнь?
  4. Если Aproperty of AClass не вводится, то сложнее его издеваться?
  5. Возвращаясь к первому вопросу. Если использование new object () плохо, почему мы внедряем реализацию, а не интерфейс? Я думаю, что многие из вас говорят, что мы фактически внедряем интерфейс, но конфигурация заставляет вас указать реализацию этого интерфейса ... не во время выполнения ... он жестко запрограммирован во время компиляции.

Это основано на ответе @ Adam N опубликовано.

Почему PersonService больше не нужно беспокоиться о GroupMembershipService? Вы только что упомянули, что GroupMembership имеет несколько вещей (объектов / свойств), от которых зависит. Если в PService требуется GMService, он будет иметь свойство. Вы можете сделать это вне зависимости от того, вводили вы это или нет. Единственный раз, когда я хотел бы, чтобы его вводили, - это если бы в GMService были более конкретные дочерние классы, которые вы не знали бы до времени выполнения. Тогда вы захотите внедрить подкласс. Или, если вы хотите использовать это как синглтон или прототип. Честно говоря, в файле конфигурации есть все, что жестко запрограммировано в том, что касается подкласса для типа (интерфейса), который он собирается внедрить во время компиляции.

РЕДАКТИРОВАТЬ

Хороший комментарий Хосе Мария Арранц на DI

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

Ложь. Направление зависимостей находится в форме XML или в виде аннотаций, ваши зависимости записываются в виде XML-кода и аннотаций. XML и аннотации являются исходным кодом.

DI уменьшает связь, делая все ваши компоненты модульными (т.е. заменяемыми) и имеет четко определенные интерфейсы друг с другом.

Ложь. Вам не нужна структура DI для построения модульного кода на основе интерфейсов.

О заменяемых: с очень простым архивом .properties и Class.forName вы можете определить, какие классы можно изменить. Если ЛЮБОЙ класс вашего кода можно изменить, Java не для вас, используйте язык сценариев. Кстати, аннотации нельзя изменить без перекомпиляции.

По моему мнению, есть одна единственная причина для каркасов DI: уменьшение котельной плиты. С хорошо сделанной фабричной системой вы можете сделать то же самое, более управляемое и более предсказуемое, что и предпочитаемая вами DI-среда, DI-структуры обещают сокращение кода (XML и аннотации также являются исходным кодом). Проблема заключается в том, что это сокращение базовой платы просто реально в очень очень простых случаях (один экземпляр на класс и аналогичные), иногда в реальном мире выбор подходящего объекта обслуживания не так прост, как сопоставление класса с одноэлементным объектом.


8

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

$foo = Foo->new($bar);

Мы просто называем это передачей параметров в конструктор. Мы занимаемся этим регулярно с тех пор, как были изобретены конструкторы.

«Внедрение зависимостей» считается типом «инверсии управления», что означает, что некоторая логика удалена из вызывающей стороны. Это не тот случай, когда вызывающая сторона передает параметры, поэтому, если бы это был DI, DI не означало бы инверсию управления.

DI означает, что существует промежуточный уровень между вызывающей стороной и конструктором, который управляет зависимостями. Makefile - простой пример внедрения зависимостей. «Вызывающий» - это человек, набирающий «make bar» в командной строке, а «constructor» - это компилятор. Makefile указывает, что bar зависит от foo, и делает

gcc -c foo.cpp; gcc -c bar.cpp

прежде чем делать

gcc foo.o bar.o -o bar

Человеку, печатающему «make bar», не обязательно знать, что bar зависит от foo. Зависимость была введена между "make bar" и gcc.

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

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


8

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

(и ps, да, это стало чрезмерно раскрученным названием за 25 $ для довольно простой, концепции) , мои .25центы


8

Я знаю, что ответов уже много, но я нашел это очень полезным: http://tutorials.jenkov.com/dependency-injection/index.html

Нет зависимости:

public class MyDao {

  protected DataSource dataSource = new DataSourceImpl(
    "driver", "url", "user", "password");

  //data access methods...
  public Person readPerson(int primaryKey) {...}     
}

Зависимость:

public class MyDao {

  protected DataSource dataSource = null;

  public MyDao(String driver, String url, String user, String password) {
    this.dataSource = new DataSourceImpl(driver, url, user, password);
  }

  //data access methods...
  public Person readPerson(int primaryKey) {...}
}

Обратите внимание, как DataSourceImplэкземпляр перемещается в конструктор. Конструктор принимает четыре параметра, которые являются четырьмя значениями, необходимыми для DataSourceImpl. Хотя MyDaoкласс все еще зависит от этих четырех значений, он сам больше не удовлетворяет этим зависимостям. Они предоставляются любым классом, создающим MyDaoэкземпляр.


1
Разве DI не передаст вам интерфейс, уже созданный DataSourceImp?
PmanAce

6

Инъекция зависимости является одним из возможных решений того, что обычно называют требованием «обфускации зависимости». Запутывание зависимостей - это метод, позволяющий исключить «очевидную» природу из процесса предоставления зависимости классу, который требует ее, и, следовательно, каким-то образом запутывать предоставление указанной зависимости указанному классу. Это не обязательно плохо. Фактически, запутывая способ предоставления зависимости классу, что-то за пределами класса отвечает за создание зависимости, что означает, что в различных сценариях может быть предоставлена ​​другая реализация зависимости без внесения каких-либо изменений. к классу. Это отлично подходит для переключения между рабочим и тестовым режимами (например, с использованием «фиктивной» зависимости сервиса).

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

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



5

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

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

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

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

Теперь можно сказать, что все зависимости для сборки автомобиля внедряются на заводе от поставщиков . Это пример реальной инъекции зависимости (DI) .

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

Это поможет вам теперь изучить DI с помощью нескольких слов. Это покажет, когда использовать DI, а когда нет .

Все в одном автомобильном заводе,

Простой автомобильный завод


1
четкий ответ из 40 иш. Пример из жизни и образы. +1. Должен быть принятый ответ.
Марке Реми

4

из книги Apress.Spring.Persistence.with.Hibernate.Oct.2010

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


4

Внедрение зависимостей (DI) является одним из шаблонов проектирования, в котором используется основная функция ООП - связь в одном объекте с другим объектом. В то время как наследование наследует один объект, чтобы сделать более сложный и конкретный другой объект, связь или ассоциация просто создает указатель на другой объект из одного объекта с использованием атрибута. Мощность DI сочетается с другими функциями ООП, такими как интерфейсы и скрытый код. Предположим, у нас есть клиент (подписчик) в библиотеке, который может для простоты заимствовать только одну книгу.

Интерфейс книги:

package com.deepam.hidden;

public interface BookInterface {

public BookInterface setHeight(int height);
public BookInterface setPages(int pages);   
public int getHeight();
public int getPages();  

public String toString();
}

Далее у нас может быть много разных книг; один из типов это фантастика:

package com.deepam.hidden;

public class FictionBook implements BookInterface {
int height = 0; // height in cm
int pages = 0; // number of pages

/** constructor */
public FictionBook() {
    // TODO Auto-generated constructor stub
}

@Override
public FictionBook setHeight(int height) {
  this.height = height;
  return this;
}

@Override
public FictionBook setPages(int pages) {
  this.pages = pages;
  return this;      
}

@Override
public int getHeight() {
    // TODO Auto-generated method stub
    return height;
}

@Override
public int getPages() {
    // TODO Auto-generated method stub
    return pages;
}

@Override
public String toString(){
    return ("height: " + height + ", " + "pages: " + pages);
}
}

Теперь подписчик может иметь ассоциацию с книгой:

package com.deepam.hidden;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Subscriber {
BookInterface book;

/** constructor*/
public Subscriber() {
    // TODO Auto-generated constructor stub
}

// injection I
public void setBook(BookInterface book) {
    this.book = book;
}

// injection II
public BookInterface setBook(String bookName) {
    try {
        Class<?> cl = Class.forName(bookName);
        Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructor
        BookInterface book = (BookInterface) constructor.newInstance();
        //book = (BookInterface) Class.forName(bookName).newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return book;
}

public BookInterface getBook() {
  return book;
}

public static void main(String[] args) {

}

}

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

package com.deepam.implement;

import com.deepam.hidden.Subscriber;
import com.deepam.hidden.FictionBook;

public class CallHiddenImplBook {

public CallHiddenImplBook() {
    // TODO Auto-generated constructor stub
}

public void doIt() {
    Subscriber ab = new Subscriber();

    // injection I
    FictionBook bookI = new FictionBook();
    bookI.setHeight(30); // cm
    bookI.setPages(250);
    ab.setBook(bookI); // inject
    System.out.println("injection I " + ab.getBook().toString());

    // injection II
    FictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and set
    System.out.println("injection II " + ab.getBook().toString());      
}

public static void main(String[] args) {
    CallHiddenImplBook kh = new CallHiddenImplBook();
    kh.doIt();
}
}

Есть много разных способов использования внедрения зависимостей. Можно комбинировать его с Singleton и т. Д., Но в основном это только ассоциация, реализованная путем создания атрибута типа объекта внутри другого объекта. Полезность только и только в том, что код, который мы должны писать снова и снова, всегда готов и сделан для нас вперед. Вот почему DI так тесно связан с Inversion of Control (IoC), что означает, что наша программа передает управление другому работающему модулю, который делает инъекции bean-компонентов в наш код. (Каждый объект, который может быть введен, может быть подписан или рассматриваться как Бин.) Например, в Spring это делается путем создания и инициализации. ApplicationContext.контейнер, который делает эту работу для нас. Мы просто в нашем коде создаем Context и вызываем инициализацию bean-компонентов. В этот момент инъекция была сделана автоматически.


4

Инъекция зависимости для 5 лет.

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

То, что вы должны делать, это заявить о необходимости: «Мне нужно что-нибудь выпить с обедом», и тогда мы позаботимся о том, чтобы у вас было что-нибудь, когда вы сядете поесть.


1
Это явно ответ родителей. ;)
Марке Реми

4

Из книги Кристоффера Норинга, книги Пабло Дилемана «Углубленное изучение - второе издание»:

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

От: Антон Моисеев. книга «Угловое развитие с Typescript, второе издание».

«Короче говоря, DI помогает вам писать код в слабосвязанной форме и делает ваш код более тестируемым и пригодным для повторного использования ».


3

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

DI - это реализация принципала Spring в МОК, который гласит: «Не звоните нам, мы вам позвоним». При использовании инжекции зависимостей программисту не нужно создавать объект, используя ключевое слово new.

Объекты однажды загружаются в контейнер Spring, а затем мы используем их всякий раз, когда они нам нужны, выбирая эти объекты из контейнера Spring с помощью метода getBean (String beanName).


3

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

На самом деле, предположим, что в Java вы создали два разных класса, как класс A и класс B, и любую функцию, доступную в классе B, которую вы хотите использовать в классе A, так что в этот момент можно использовать внедрение зависимостей. где вы можете создать объект одного класса в другом, таким же образом вы можете внедрить целый класс в другой класс, чтобы сделать его доступным. таким образом, зависимость может быть преодолена.

ЗАВИСИМОСТЬ ОТ ЗАВИСИМОСТИ ПРОСТО СКЛЕИВАЯ ДВУХ КЛАССОВ И ОДНОВРЕМЕННО СОХРАНЯЯ ИХ.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.