Что такое внедрение зависимостей? - PullRequest
2885 голосов
/ 25 сентября 2008

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

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

Ответы [ 34 ]

35 голосов
/ 28 октября 2016

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

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

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

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

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

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

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

Switch -> Bulb1 ИЛИ Bulb2 ИЛИ NightBulb (введенная зависимость)

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

Изменение Джеймс Пример выключателя и лампочки:

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(); 
    ... 
  } 
}`
25 голосов
/ 23 сентября 2013

Весь смысл внедрения зависимостей (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-фреймворках.

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

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

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

18 голосов
/ 03 ноября 2017

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

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

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

Reel-to-reel portable tape recorder, mid-20th century.

Приведенное выше изображение является изображением портативного магнитофона 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 голосов
/ 04 декабря 2015

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

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

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

Нам не нужно жестко кодировать объект с помощью нового ключевого слова, а определить зависимость 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-компонентов после вызова конструктора без аргументов или статического метода фабрики без аргументов для создания экземпляра вашего компонента.

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 голосов
/ 05 апреля 2016

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

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

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

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

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

13 голосов
/ 07 января 2014

Это означает, что у объектов должно быть только столько зависимостей, сколько необходимо для их работы, и зависимостей должно быть немного. Кроме того, зависимости объекта должны быть по интерфейсам, а не по «конкретным» объектам, когда это возможно. (Конкретный объект - это любой объект, созданный с помощью ключевого слова 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;
13 голосов
/ 14 марта 2018

Например, у нас есть 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();

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

  • Simple

Недостатки

  • Тяжело для теста 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, вам нужно только изменить его в классе инжекторов
  • Если вы используете Constructor Injection, когда вы посмотрите на конструктор Client, вы увидите, сколько зависимостей Client class

Недостатки

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

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

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

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

10 голосов
/ 25 октября 2013

Я думаю, что поскольку все написали для 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 голосов
/ 10 декабря 2015

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

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

7 голосов
/ 02 июня 2015

Популярные ответы бесполезны, потому что они определяют внедрение зависимостей таким образом, что это бесполезно. Давайте согласимся, что под «зависимостью» мы подразумеваем некоторый ранее существующий другой объект, который нужен нашему объекту 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.

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

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

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