Что такое внедрение зависимостей?
Уже было опубликовано несколько вопросов с конкретными вопросами о внедрении зависимости, например, когда его использовать и какие существуют для него рамки. Тем не мение,
Что такое внедрение зависимостей и когда / почему его следует или не следует использовать?
41 ответ
По сути, вместо того, чтобы ваши объекты создавали зависимость или просили фабричный объект создать ее для них, вы передаете необходимые зависимости объекту извне и создаете для него чужую проблему. Этот "кто-то" является либо объектом, находящимся выше в графе зависимостей, либо инжектором зависимостей (каркасом), который строит граф зависимостей. Зависимость, которую я использую здесь, - это любой другой объект, на который текущий объект должен ссылаться.
Одним из основных преимуществ внедрения зависимостей является то, что он может облегчить тестирование. Предположим, у вас есть объект, который в своем конструкторе делает что-то вроде:
public SomeClass() {
myObject = Factory.getObject();
}
Это может быть хлопотно, когда все, что вы хотите сделать, это запустить несколько юнит-тестов на SomeClass
особенно если myObject
это то, что делает сложным доступ к диску или сети. Итак, теперь вы смотрите на насмешки myObject
но также как-то перехватывает вызов фабрики. Жесткий. Вместо этого передайте объект в качестве аргумента конструктору. Теперь вы перенесли проблему в другое место, но тестирование может стать намного проще. Просто сделай манекен myObject
и передайте это. Конструктор теперь будет выглядеть примерно так:
public SomeClass (MyClass myObject) {
this.myObject = myObject;
}
Это один стиль внедрения зависимостей - через конструктор. Возможны несколько механизмов.
- Как отмечается в комментариях, одной из распространенных альтернатив является определение конструктора, который ничего не делает, и чтобы зависимости были введены через установщики свойств (h/t @MikeVella).
- Мартин Фаулер описывает третью альтернативу (h/t @MarcDix), где классы явно реализуют интерфейс для зависимостей, которые они хотят внедрить.
Когда не используется внедрение зависимостей (например, в классах, которые слишком много работают в своих конструкторах и т. Д.), Становится намного сложнее изолировать компоненты в модульном тестировании.
Еще в 2013 году, когда я писал этот ответ, эта тема была основной в блоге Google Testing. Это остается для меня самым большим преимуществом, поскольку вам не всегда может понадобиться дополнительная гибкость в дизайне среды выполнения (например, для поиска служб или аналогичных шаблонов), но вам часто нужно иметь возможность изолировать свои классы во время тестирования.
Лучшее определение, которое я нашел до сих пор, - это определение Джеймса Шора:
"Инъекция зависимости" - это понятие за 5 центов на 25 долларов. [...] Внедрение зависимости означает предоставление объекту его переменных экземпляра. [...].
Есть статья Мартина Фаулера, которая также может оказаться полезной.
Внедрение зависимостей в основном обеспечивает объекты, в которых нуждается объект (его зависимости), вместо того, чтобы он сам их конструировал. Это очень полезный метод для тестирования, поскольку он позволяет смоделировать или заглушить зависимости.
Зависимости могут быть введены в объекты многими способами (например, инжектор конструктора или сеттер). Для этого можно даже использовать специализированные структуры внедрения зависимостей (например, Spring), но они, конечно, не требуются. Вам не нужны эти структуры для внедрения зависимости. Инстанцирование и передача объектов (зависимостей) в явном виде - такая же хорошая инъекция, как и внедрение через фреймворк.
Я нашел этот забавный пример с точки зрения слабой связи:
Любое приложение состоит из множества объектов, которые взаимодействуют друг с другом для выполнения некоторых полезных задач. Традиционно каждый объект отвечает за получение своих собственных ссылок на зависимые объекты (зависимости), с которыми он сотрудничает. Это приводит к высокосвязанным классам и трудно тестируемому коду.
Например, рассмотрим 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;
}
}
Источник: Понимание внедрения зависимости
Внедрение зависимостей - это практика, при которой объекты проектируются таким образом, что они получают экземпляры объектов из других частей кода, а не конструируют их внутренне. Это означает, что любой объект, реализующий интерфейс, который требуется объекту, может быть заменен без изменения кода, что упрощает тестирование и улучшает разделение.
Например, рассмотрим эти предложения:
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
для тестирования или для использования шаблона стратегии в различных частях вашего приложения.
С Внедрением Зависимости, вместо того, чтобы создавать GroupMembershipService
в вашем PersonService
, вы бы либо передать его в PersonService
конструктор, или добавить свойство (геттер и сеттер), чтобы установить его локальный экземпляр. Это означает, что ваш PersonService
больше не нужно беспокоиться о том, как создать GroupMembershipService
, он просто принимает те, которые ему дали, и работает с ними. Это также означает, что все, что является подклассом GroupMembershipService
или реализует GroupMembershipService
интерфейс может быть "введен" в PersonService
и PersonService
не нужно знать об изменении.
Давайте попробуем простой пример с классами 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 подключается к той же системе сервисов.
Принятый ответ - хороший, но я хотел бы добавить к этому, что DI очень похож на классический отказ от жестко закодированных констант в коде.
Когда вы используете некоторую константу, такую как имя базы данных, вы быстро перемещаете ее изнутри кода в некоторый файл конфигурации и передаете переменную, содержащую это значение, в место, где оно необходимо. Причиной этого является то, что эти константы обычно меняются чаще, чем остальная часть кода. Например, если вы хотите проверить код в тестовой базе данных.
DI аналогичен этому в мире объектно-ориентированного программирования. Значения там вместо константных литералов являются целыми объектами - но причина удаления кода, создающего их из кода класса, аналогична - объекты меняются чаще, чем код, который их использует. Одним из важных случаев, когда необходимо такое изменение, являются тесты.
Давайте представим, что вы хотите пойти на рыбалку:
Без внедрения зависимостей, вы должны позаботиться обо всем самостоятельно. Вам нужно найти лодку, купить удочку, найти приманку и т. Д. Это, конечно, возможно, но это накладывает на вас большую ответственность. С точки зрения программного обеспечения это означает, что вы должны выполнить поиск всех этих вещей.
С помощью инъекции зависимостей кто-то другой позаботится обо всей подготовке и предоставит вам необходимое оборудование. Вы получите ("впрысните") лодку, удочку и приманку - все готово к использованию.
Это самое простое объяснение контейнера ввода зависимостей и внедрения зависимостей, которое я когда-либо видел:
Без внедрения зависимостей
- Приложению требуется Foo (например, контроллер), поэтому:
- Приложение создает Foo
- Приложение вызывает Foo
- Foo нуждается в Bar (например, услуга), поэтому:
- Фу создает бар
- Фу называет бар
- Бар нужен Bim (сервис, хранилище, …), поэтому:
- Бар создает Бим
- Бар что-то делает
С инъекцией зависимостей
- Приложению нужен Foo, которому нужен Bar, которому нужен Bim, поэтому:
- Приложение создает Bim
- Приложение создает бар и дает ему Бим
- Приложение создает Foo и дает ему Bar
- Приложение вызывает Foo
- Фу называет бар
- Бар что-то делает
- Фу называет бар
Использование контейнера для инъекций зависимостей
- Приложение нуждается в Foo так:
- Приложение получает Foo из контейнера, поэтому:
- Контейнер создает Бим
- Контейнер создает Бар и дает ему Бим
- Контейнер создает Foo и дает ему Bar
- Приложение вызывает Foo
- Фу называет бар
- Бар что-то делает
- Фу называет бар
Инъекция зависимости и Инъекция зависимости - это разные вещи:
- Dependency Injection - это метод для написания лучшего кода
- DI-контейнер - это инструмент, помогающий внедрять зависимости
Вам не нужен контейнер для внедрения зависимостей. Однако контейнер может помочь вам.
Прежде чем переходить к техническому описанию, сначала визуализируйте его на реальном примере, потому что вы найдете много технических вещей для изучения внедрения зависимостей, но максимальное время, когда люди вроде меня не могут понять его основную концепцию.
На первом рисунке предположим, что у вас есть автомобильный завод с большим количеством узлов. Автомобиль фактически собирается в сборочной единице, но для него нужны не только колеса, но и двигатель, и сиденья. Таким образом, сборочная единица зависит от всех этих единиц, и они являются зависимостью завода.
Вы можете почувствовать, что сейчас слишком сложно поддерживать все задачи на этом заводе, потому что наряду с основной задачей (сборка автомобиля в сборочном узле) вы также должны сосредоточиться на других узлах. Сейчас его содержать очень дорого, а заводское здание огромно, так что вам придется потратить лишние деньги на аренду.
Теперь посмотрим на вторую картинку. Если вы найдете компании-поставщики, которые предоставят вам колеса, сиденья и двигатель дешевле, чем затраты на самостоятельное производство, то теперь вам не нужно производить их на своем заводе. Теперь вы можете арендовать меньшее здание только для вашей сборочной единицы, что уменьшит ваши задачи по обслуживанию и уменьшит ваши дополнительные расходы на аренду. Теперь вы также можете сосредоточиться только на своей основной задаче (сборка автомобиля).
Теперь можно сказать, что все зависимости для сборки автомобиля закачиваются на заводе от провайдеров. Это пример реальной инъекции зависимостей (DI).
Говоря техническим языком, внедрение зависимостей - это метод, с помощью которого один объект (или статический метод) предоставляет зависимости другого объекта. Итак, передача задачи создания объекта кому-то другому и прямое использование зависимости называется внедрением зависимости.
Это поможет вам теперь выучить DI с помощью некоторых технических слов. Это покажет, когда использовать DI, а когда нет.
.
Разве "внедрение зависимостей" не означает просто использование параметризованных конструкторов и открытых сеттеров?
Статья Джеймса Шора показывает следующие примеры для сравнения.
Конструктор без внедрения зависимостей:
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();
...
}
}
Сделать концепцию внедрения зависимостей простой для понимания. Давайте рассмотрим пример кнопки переключения для включения / выключения лампы.
Без внедрения зависимостей
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();
...
}
}`
Что такое инъекция зависимостей (DI)?
Как уже говорили другие, внедрение зависимостей (DI) устраняет ответственность за непосредственное создание и управление продолжительностью жизни других экземпляров объектов, от которых зависит наш класс интересов (потребительский класс) (в смысле UML). Эти экземпляры вместо этого передаются в наш потребительский класс, как правило, в качестве параметров конструктора или через установщики свойств (управление экземпляром объекта зависимости и передачей в потребительский класс обычно выполняется контейнером Inversion of Control (IoC), но это другая тема),
DI, DIP и SOLID
В частности, в парадигме твердых принципов Роберта С. Мартина объектно-ориентированного проектирования, DI
является одной из возможных реализаций принципа инверсии зависимости (DIP). DIP является D
из SOLID
Мантра - другие реализации DIP включают в себя сервисный локатор и шаблоны плагинов.
Цель 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
(Джава). Таким образом, используя инъекцию конструктора в приведенном выше примере, мы получаем:
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());
}
Следующие шаги
Внедрение зависимостей неизменно связано с контейнером Inversion of Control (IoC) для внедрения (предоставления) конкретных экземпляров зависимостей и управления экземплярами продолжительности жизни. Во время процесса настройки / начальной загрузки IoC
Контейнеры позволяют определить следующее:
- отображение между каждой абстракцией и сконфигурированной конкретной реализацией (например, "каждый раз, когда потребитель запрашивает
IBar
, вернутьConcreteBar
экземпляр ") - Политики могут быть настроены для управления продолжительностью жизни каждой зависимости, например, для создания нового объекта для каждого экземпляра-потребителя, для совместного использования экземпляра одноэлементной зависимости для всех потребителей, для совместного использования одного и того же экземпляра зависимости только для одного и того же потока и т. д.
- В.Net контейнеры IoC знают о таких протоколах, как
IDisposable
и возьмет на себя ответственностьDisposing
зависимости в соответствии с настроенным управлением продолжительностью жизни.
Как правило, после настройки / загрузки контейнеров IoC они работают в фоновом режиме, позволяя кодеру сосредоточиться на имеющемся коде, а не беспокоиться о зависимостях.
Ключ к DI-дружественному коду заключается в том, чтобы избежать статической связи классов и не использовать new() для создания зависимостей.
Как в приведенном выше примере, разделение зависимостей требует определенных усилий по проектированию, и для разработчика существует смена парадигмы, необходимая для избавления от привычки new
непосредственно связывая зависимости, и вместо этого доверяя контейнеру управлять зависимостями.
Но преимуществ много, особенно в том, что касается возможности тщательно проверить свой класс интересов.
Примечание: создание / отображение / проекция (через new ..()
) POCO / POJO / Сериализация DTO / Графы сущностей / Анонимные проекции JSON и др. - т.е. классы или записи "Только данные" - используемые или возвращаемые из методов, не рассматриваются как Зависимости (в смысле UML) и не подлежат DI. С помощью new
спроектировать это просто отлично.
Все вышеприведенные ответы хороши, моя цель - объяснить концепцию простым способом, чтобы любой, кто не обладает знаниями в области программирования, также мог понять концепцию.
Внедрение зависимостей является одним из шаблонов проектирования, которые помогают нам создавать сложные системы более простым способом.
Мы можем видеть широкое применение этого шаблона в нашей повседневной жизни. Некоторые из примеров: магнитофон, VCD, CD-привод и т. Д.
Это изображение представляет собой портативный магнитофон Reel-to-reel, середина 20-го века. Источник
Основным назначением магнитофона является запись или воспроизведение звука.
При разработке системы требуется катушка для записи или воспроизведения звука или музыки. Есть две возможности для проектирования этой системы
- мы можем разместить катушку внутри машины
- мы можем предоставить крючок для катушки, где он может быть размещен.
Если мы используем первый, нам нужно открыть машину, чтобы сменить катушку. если мы выберем второй вариант, который устанавливает крюк для барабана, мы получаем дополнительное преимущество от воспроизведения любой музыки, меняя барабан. а также уменьшая функцию только для воспроизведения чего-либо на барабане.
Подобным образом, внедрение зависимостей - это процесс экстернализации зависимостей, чтобы сосредоточиться только на конкретной функциональности компонента, чтобы независимые компоненты могли быть соединены вместе, чтобы сформировать сложную систему.
Основные преимущества мы достигли с помощью внедрения зависимостей.
- Высокая когезия и слабое сцепление.
- Выносить зависимость и смотреть только на ответственность.
- Делать вещи как компоненты и объединять в большие системы с высокими возможностями.
- Это помогает разрабатывать высококачественные компоненты, так как они разрабатываются независимо, они должным образом проверены.
- Это помогает заменить компонент другим, если один выходит из строя.
В наши дни эта концепция лежит в основе известных фреймворков в мире программирования. 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();
}
Вы также можете прочитать
Смысл 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
Реализация сценария непосредственно в зависимом, вы используете 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-фреймворках.
Но это общий подход для разделения проблем:
- разработка приложений (жизненный цикл выпускаодного исходного кода)
- развертывание приложения (несколько целевых сред с независимыми жизненными циклами)
Использование конфигурации только с поиском зависимостей не помогает, так как количество параметров конфигурации может изменяться для каждой зависимости (например, новый тип аутентификации), а также количество поддерживаемых типов зависимостей (например, новый тип базы данных).
Например, у нас есть 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
, вам нужно только изменить его в классе инжекторов - Если вы используете использовать
Constructor Injection
, когда вы смотрите на конструкторClient
, вы увидите, сколько зависитClient
учебный класс
Недостатки
- Если вы используете использовать
Constructor Injection
,Service
объект создается когдаClient
создано, иногда мы используем функцию вClient
класс без использованияService
так созданоService
впустую
Определение внедрения зависимости
https://en.wikipedia.org/wiki/Dependency_injection
Зависимость - это объект, который можно использовать (
Service
)
Внедрение - это передача зависимости (Service
) к зависимому объекту (Client
) что бы это использовать
Что такое инъекция зависимости?
Внедрение зависимостей (DI) означает разделение объектов, которые зависят друг от друга. Скажем, объект A зависит от объекта B, поэтому идея состоит в том, чтобы отделить эти объекты друг от друга. Нам не нужно жестко кодировать объект с использованием нового ключевого слова, а делиться зависимостями с объектами во время выполнения, несмотря на время компиляции. Если мы говорим о
Как работает Dependency Injection весной:
Нам не нужно жестко кодировать объект, используя ключевое слово new, а определить зависимость bean-компонента в файле конфигурации. Пружинный контейнер будет отвечать за подключение всех.
Инверсия Контроля (МОК)
МОК является общей концепцией, и ее можно выразить многими различными способами, а внедрение зависимости является одним из конкретных примеров МОК.
Два типа внедрения зависимостей:
- Конструктор Инъекция
- Сеттер Инъекция
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 для установщика можно использовать для создания сеттеров в качестве необходимых зависимостей.
Лучшая аналогия, которую я могу придумать, - это хирург и его ассистент (ы) в операционном зале, где хирург является главным человеком и его ассистентом, который предоставляет различные хирургические компоненты, когда ему это нужно, чтобы хирург мог сосредоточиться на одном. что он делает лучше всего (операция). Без ассистента хирург должен сам получать компоненты каждый раз, когда он ему нужен.
DI для краткости - это метод, позволяющий снять общую дополнительную ответственность (нагрузку) с компонентов за выборку зависимых компонентов, предоставляя их им.
DI приближает вас к принципу единой ответственности (SR), например surgeon who can concentrate on surgery
,
Когда использовать DI: Я бы рекомендовал использовать DI почти во всех производственных проектах (малых / больших), особенно в постоянно меняющихся бизнес-средах:)
Почему: потому что вы хотите, чтобы ваш код был легко тестируемым, макетируемым и т. Д., Чтобы вы могли быстро протестировать свои изменения и вывести их на рынок. Кроме того, почему бы вам этого не сделать, когда у вас есть множество отличных бесплатных инструментов / фреймворков, которые помогут вам в вашем путешествии к базе кода, где у вас больше контроля.
Это означает, что у объектов должно быть столько зависимостей, сколько необходимо для их работы, а зависимостей должно быть немного. Кроме того, зависимости объекта должны быть по интерфейсам, а не по "конкретным" объектам, когда это возможно. (Конкретный объект - это любой объект, созданный с помощью ключевого слова new.) Свободное связывание способствует большей возможности повторного использования, облегчает обслуживание и позволяет легко предоставлять "фиктивные" объекты вместо дорогих услуг.
"Инъекция зависимости" (DI) также известна как "Инверсия контроля" (IoC) и может использоваться в качестве метода для поощрения этой слабой связи.
Существует два основных подхода к реализации DI:
- Конструктор инъекций
- Сеттер впрыска
Конструктор инъекций
Это метод передачи зависимостей объекта его конструктору.
Обратите внимание, что конструктор принимает интерфейс, а не конкретный объект. Также обратите внимание, что возникает исключение, если параметр orderDao имеет значение null. Это подчеркивает важность получения действительной зависимости. На мой взгляд, Constructor Injection является предпочтительным механизмом для предоставления объекту его зависимостей. При вызове объекта для разработчика ясно, какие зависимости необходимо передать объекту "Person" для правильного выполнения.
Сеттер Инъекция
Но рассмотрим следующий пример... Предположим, у вас есть класс с десятью методами, которые не имеют зависимостей, но вы добавляете новый метод, который имеет зависимость от IDAO. Вы можете изменить конструктор, чтобы использовать конструктор Injection, но это может заставить вас вносить изменения во все вызовы конструктора повсюду. В качестве альтернативы, вы можете просто добавить новый конструктор, который получает зависимость, но тогда как разработчик легко узнает, когда использовать один конструктор над другим. Наконец, если создать зависимость очень дорого, зачем ее создавать и передавать конструктору, если ее можно использовать редко? "Инъекция сеттера" - это еще один метод DI, который можно использовать в таких ситуациях, как эта.
Внедрение сеттера не заставляет зависимости передаваться в конструктор. Вместо этого зависимости устанавливаются на общедоступные свойства, предоставляемые нуждающимся объектом. Как подразумевалось ранее, основными мотиваторами для этого являются:
- Поддержка внедрения зависимостей без необходимости изменения конструктора унаследованного класса.
- Позволяет создавать дорогостоящие ресурсы или услуги как можно позже и только при необходимости.
Вот пример того, как будет выглядеть приведенный выше код:
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;
Я знаю, что ответов уже много, но я нашел это очень полезным: 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
пример.
Я думаю, так как все написали для DI, позвольте мне задать несколько вопросов..
- Если у вас есть конфигурация DI, в которой все фактические реализации (не интерфейсы), которые будут внедрены в класс (например, для служб контроллера), почему это не какое-то жесткое кодирование?
- Что если я захочу изменить объект во время выполнения? Например, мой конфиг уже говорит, когда я создаю экземпляр MyController, введите для FileLogger как ILogger. Но я мог бы хотеть ввести DatabaseLogger.
- Каждый раз, когда я хочу изменить объекты, которые нужны моему AClass, мне нужно теперь искать в двух местах - сам класс и файл конфигурации. Как это облегчает жизнь?
- Если Aproperty of AClass не вводится, то сложнее его издеваться?
- Возвращаясь к первому вопросу. Если использование 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 и аннотации также являются исходным кодом). Проблема заключается в том, что это сокращение базовой платы является просто реальным в очень очень простых случаях (один экземпляр на класс и аналогичные), иногда в реальном мире выбор подходящего сервисного объекта не так прост, как сопоставление класса с одноэлементным объектом.
Внедрение зависимостей означает способ (фактически любым способом) для одной части кода (например, класса) иметь доступ к зависимостям (другим частям кода, например, другим классам, от которых это зависит) модульным способом без их жесткого кодирования (поэтому они могут быть изменены или могут быть переопределены свободно, или даже могут быть загружены в другое время, если это необходимо)
(и пс, да, это стало чрезмерно раскрученным названием за 25$ для довольно простой, концепции), мой .25
центов
Популярные ответы бесполезны, потому что они определяют внедрение зависимостей таким образом, что это бесполезно. Давайте согласимся, что под "зависимостью" мы подразумеваем некоторый ранее существующий другой объект, в котором нуждается наш объект 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.
Основная цель промежуточного уровня - не просто передать зависимости конструктору, но перечислить все зависимости в одном месте и спрятать их от кодера (чтобы кодер не предоставлял их).
Обычно промежуточный уровень предоставляет фабрики для созданных объектов, которые должны обеспечивать роль, которой должен удовлетворять каждый запрашиваемый тип объекта. Это потому, что имея промежуточный уровень, который скрывает детали конструкции, вы уже понесли штраф за абстракцию, наложенный фабриками, так что вы также можете использовать фабрики.
Инъекция зависимости является одним из возможных решений того, что обычно называют требованием "обфускация зависимости". Обфускация зависимостей - это метод, позволяющий исключить "очевидную" природу из процесса предоставления зависимости классу, который требует ее, и, следовательно, каким-то образом запутать предоставление указанной зависимости указанному классу. Это не обязательно плохо. Фактически, запутывая способ, которым зависимость предоставляется классу, что-то вне класса отвечает за создание зависимости, что означает, что в различных сценариях может быть предоставлена другая реализация зависимости без внесения каких-либо изменений. к классу. Это отлично подходит для переключения между рабочим и тестовым режимами (например, с использованием "фиктивной" зависимости сервиса).
К сожалению, плохая часть заключается в том, что некоторые люди считают, что вам нужна специализированная среда для обфускации зависимостей, и что вы как-то "меньший" программист, если вы решите не использовать конкретную среду для этого. Другой, чрезвычайно тревожный миф, по мнению многих, состоит в том, что внедрение зависимости является единственным способом достижения обфускации зависимости. Это наглядно и исторически, и, очевидно, на 100% неправильно, но у вас будут проблемы с убеждением некоторых людей, что существуют альтернативы внедрению зависимости для ваших требований обфускации зависимости.
Программисты понимали требование обфускации зависимостей в течение многих лет, и многие альтернативные решения развивались как до, так и после зачатия зависимости. Существуют фабричные шаблоны, но есть также много опций, использующих ThreadLocal, где не требуется внедрение в конкретный экземпляр - зависимость эффективно внедряется в поток, что дает преимущество в том, что объект становится доступным (с помощью удобных статических методов получения) для любого класса, который требует этого, не добавляя аннотации к классам, которые этого требуют, и настраивая сложный XML-клей, чтобы это произошло. Когда ваши зависимости требуются для персистентности (JPA/JDO и т. Д.), Это позволяет вам гораздо проще добиться "прозрачной персистентности", а доменная модель и классы бизнес-моделей состоят исключительно из POJO (то есть, нет специфичной для фреймворка / заблокированной в аннотациях).
Из книги " Хорошо обоснованный Java-разработчик: жизненно важные методы Java 7 и программирование полиглотов"
DI - это особая форма IoC, в которой процесс поиска ваших зависимостей находится вне прямого контроля над выполняемым в данный момент кодом.
Инъекция зависимости для детей 5 лет.
Когда вы идете и достаете вещи из холодильника для себя, вы можете создать проблемы. Вы можете оставить дверь открытой, вы можете получить то, что мама или папа не хотят, чтобы вы имели. Возможно, вы даже ищете то, чего у нас нет или срок действия которого истек.
Вам следует заявить о своей потребности: "Мне нужно что-нибудь выпить за обедом", а затем мы позаботимся о том, чтобы у вас было что-нибудь, когда вы сядете поесть.
Инъекция зависимости (DI) является частью практики Принципа зависимости зависимости (DIP), которая также называется инверсией контроля (IoC). По сути, вам нужно делать DIP, потому что вы хотите сделать свой код более модульным и модульно тестируемым, а не только одной монолитной системой. Итак, вы начинаете идентифицировать части кода, которые можно отделить от класса и абстрагировать. Теперь реализацию абстракции нужно вводить извне класса. Обычно это можно сделать через конструктор. Таким образом, вы создаете конструктор, который принимает абстракцию в качестве параметра, и это называется внедрением зависимости (через конструктор). Более подробное описание контейнеров DIP, DI и IoC вы можете прочитать здесь.
Я бы предложил несколько иное, краткое и точное определение понятия "внедрение зависимостей", сосредоточив внимание на основной цели, а не на технических средствах (следуя далее):
Внедрение зависимостей - это процесс создания статического графа объектов службы без состояния, в котором каждый сервис параметризуется своими зависимостями.
Объекты, которые мы создаем в наших приложениях (независимо от того, используем ли мы Java, C# или другой объектно-ориентированный язык), обычно делятся на две категории: не имеющие состояния, статические и глобальные "сервисные объекты" (модули), и с состоянием, динамические и локальные "Объекты данных".
Граф модуля - граф сервисных объектов - обычно создается при запуске приложения. Это можно сделать с помощью контейнера, такого как Spring, но также можно сделать вручную, передав параметры конструкторам объектов. Оба способа имеют свои плюсы и минусы, но для использования DI в вашем приложении определенно не требуется фреймворк.
Одним из требований является то, что сервисы должны быть параметризованы их зависимостями. Что именно это означает, зависит от языка и подхода, используемого в данной системе. Обычно это принимает форму параметров конструктора, но использование сеттеров также является опцией. Это также означает, что зависимости службы скрыты (при вызове метода службы) от пользователей службы.
Когда использовать? Я бы сказал, всякий раз, когда приложение достаточно велико, чтобы инкапсулировать логику в отдельные модули, а граф зависимостей между модулями дает улучшение читабельности и возможности изучения кода.
Из книги Кристоффера Норинга, книги Пабло Дилемана "Углубленное изучение - второе издание":
"По мере того как наши приложения растут и развиваются, каждому из наших объектов кода внутренне потребуются экземпляры других объектов, которые более известны как зависимости в мире разработки программного обеспечения. Действие передачи таких зависимостей зависимому клиенту называется внедрением, и это также влечет за собой участие другого объекта кода, называемого инжектором. Инжектор будет нести ответственность за создание и загрузку требуемых зависимостей, чтобы они были готовы к использованию с того самого момента, как они успешно внедрены в клиент. Это очень важно, поскольку клиент ничего не знает о том, как создавать свои собственные зависимости, и знает только интерфейс, который они реализуют, чтобы использовать их ".
Внедрение зависимостей (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-компонентов. В этот момент инъекция была сделана автоматически.
Проще говоря, внедрение зависимостей (DI) - это способ удалить зависимости или тесную связь между различными объектами. Внедрение зависимостей дает связное поведение каждому объекту.
DI - это реализация принципала Spring в МОК, который гласит: "Не звоните нам, мы позвоним вам". При использовании инжекции зависимостей программисту не нужно создавать объект, используя ключевое слово new.
Объекты однажды загружаются в контейнер Spring, а затем мы используем их всякий раз, когда они нам нужны, выбирая эти объекты из контейнера Spring с помощью метода getBean(String beanName).