Що таке ін'єкційна залежність?


3074

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

Що таке ін'єкція в залежність і коли / чому слід або не слід застосовувати її?


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

33
Я згоден з коментарями стосовно посилань. Я можу зрозуміти, що ви можете посилатися на когось іншого. Але принаймні додайте, чому ви пов’язуєте їх, і що робить це посилання кращим, ніж інші посилання, які я міг отримати за допомогою google
Крістіан Пейн

@AR: Технічно введення залежностей не є особливою формою IoC. Скоріше, IoC - це одна з методик, яка використовується для забезпечення ін'єкції залежності. Інші методи можуть бути використані для забезпечення ін'єкції залежності (хоча IoC є єдиним загальним способом використання), а IoC використовується також і для багатьох інших проблем.
Шон Рейлі

Одне з найприємніших пояснень, про які я коли-небудь читав про DI, - це "Guice" від Google (вимовляється як сік) http://code.google.com/p/google-guice/wiki/Motivation?tm=6
Радж,

136
Щодо посилань, пам’ятайте, що вони часто так чи інакше зникають. У відповідях на ЗО зростає кількість мертвих посилань. Отже, якою б хорошою не була зв'язана стаття, це зовсім не добре, якщо ви не можете її знайти.
DOK

Відповіді:


1931

Залежність Ін'єкція - це передача залежності іншим об'єктам або рамкам (інжектор залежності).

Ін'єкційна залежність спрощує тестування. Ін'єкцію можна здійснити через конструктор .

SomeClass() має свій конструктор наступним чином:

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

Проблема : Якщо myObjectпов'язані складні завдання, такі як доступ до диска або доступ до мережі, важко зробити перевірку блоку SomeClass(). Програмісти повинні знущатися myObjectі можуть перехопити заводський виклик.

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

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

myObject можна пройти безпосередньо, що полегшує тестування.

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

Важко виділити компоненти в одиничному тестуванні без введення залежності.

У 2013 році, коли я писав цю відповідь, це була основна тема в блозі тестування Google . Для мене це залишається найбільшою перевагою, оскільки програмістам не завжди потрібна додаткова гнучкість у дизайні часу виконання (наприклад, для локатора обслуговування або подібних моделей). Програмістам часто потрібно ізолювати класи під час тестування.


25
Зважаючи на те, що посилання Бен Гофштейна на статтю Мартіна Фаулера є необхідною, оскільки вказує на "обов'язково прочитане" на цю тему, я приймаю відповідь wds, оскільки вона насправді відповідає на питання тут, на ЗО.
AR.

121
+1 для пояснення та мотивації: створення об’єктів, на яких клас залежить чиїсь проблеми . Ще один спосіб сказати, що DI робить класи більш згуртованими (у них менше обов'язків).
Фурманатор

13
Ви кажете, що залежність передається "в конструктор", але, як я розумію, це не зовсім вірно. Це все-таки ін'єкція залежності, якщо залежність встановлена ​​як властивість після того, як об'єкт був ініційований, правильно?
Майк Велла

1
@MikeVella Так, це правильно. Це не має жодної реальної різниці в більшості випадків, хоча властивості, як правило, трохи більш гнучкі. Я трохи відредагую текст, щоб вказати на це.
вд

2
Один з найкращих відповідей, які я знайшов поки що, тому мені дуже цікаво вдосконалити її. У ньому відсутній опис третьої форми введення залежності: Інтерфейсна ін'єкція .
Марк Дікс

2351

Найкраще визначення, яке я знайшов поки що, - це Джеймс Шор :

"Ін'єкційна залежність" - це термін, що становить 25 доларів, для концепції 5 центів. [...] Введення залежності залежить від надання об'єкту змінних його примірників. [...].

Є стаття Мартіна Фаулера, яка також може виявитися корисною.

Введення залежності - це, головним чином, забезпечення об'єктів, необхідних об'єкту (його залежностей), а не їх конструювання. Це дуже корисна техніка для тестування, оскільки вона дозволяє знущатись або стримувати залежності.

Залежності можуть бути введені в об'єкти різними способами (наприклад, введення конструктора або введення сетера). Для цього навіть можна використовувати спеціалізовані системи введення залежності (наприклад, Spring), але вони, звичайно, не потрібні. Вам не потрібні ці рамки для введення залежності. Ігнорування та передача об'єктів (залежностей) явно є настільки ж доброю, як ін'єкція за рамками.


35
Мені подобається пояснення статті Джеймса, особливо в кінці: "Тим не менше, ти повинен дивуватися будь-якому підходу, який бере три поняття (" TripPlanner "," CabAgency "і" AirlineAgency "), перетворює їх на 9 класів, а потім додає десятки рядків коду клею та конфігурації XML до того, як буде записано єдиний рядок логіки програми. " Це те, що я бачив дуже часто (на жаль) - що введення залежності (що само собою добре, як пояснив він), неправильно використовується для надскладних речей, які могли бути зроблені простіше - закінчуючи написанням "підтримуючого" коду ...
Метт

2
Re: "Миттєве введення та передача об'єктів (залежностей) явно є настільки ж доброю ін'єкцією, як і ін'єкція за рамками." То чому люди робили рамки для цього?
dzieciou

13
З тієї ж причини, що кожен фреймворк (або, принаймні, повинен бути) записаний: тому що існує багато повторених кодів кодової панелі, які потрібно написати після досягнення певної складності. Проблема полягає в тому, що люди часто досягають рамки, навіть коли це не потрібно.
Тіаго Арраїс

14
Термін в розмірі 25 доларів для 5-відсоткової концепції помер. Ось хороша стаття, яка мені допомогла: codeproject.com/Articles/615139/…
Крістін

@Matt конфігураційні файли - це лише "Перенести рішення", яке доводиться до крайньої межі - "Відкласти рішення до фактичного виконання". На мою думку, Dagger і особливо Dagger знайшли солодке місце "Відкласти рішення до моменту складання заявки".
Thorbjørn Ravn Andersen

645

Я знайшов цей кумедний приклад з точки зору вільної зчеплення :

Будь-яка програма складається з безлічі об'єктів, які співпрацюють один з одним, щоб виконати якісь корисні речі. Традиційно кожен об'єкт несе відповідальність за отримання власних посилань на залежні об'єкти (залежності), з якими співпрацює. Це призводить до сильно поєднаних класів і важко перевіреного коду.

Наприклад, розглянемо а Car об’єкт.

А Carзалежить від колес, двигуна, палива, акумулятора тощо. Традиційно ми визначаємо марку таких залежних об'єктів поряд з визначеннямCar об'єкта.

Без впорскування залежностей (DI):

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

  //The rest
}

Тут Carоб’єкт відповідає за створення залежних об'єктів.

Що робити, якщо ми хочемо змінити тип його залежного об'єкта - скажімо Wheel- після первинних NepaliRubberWheel()проколів? Нам потрібно відтворити об'єкт "Автомобіль" з його новою залежністю 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в шина? Я не. Все, що мені потрібно зробити - це придбати у них (впорскувати через парам), встановити і ва-ла-ла! Отже, повертаючись до програмування, скажімо, проект 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що означає, що ви не можете "піддумати" aGroupMembershipService для тестування або для використання стратегічного зразка в різних частинах вашої програми.

Завдяки введенню залежності, замість того, щоб миттєво встановлювати GroupMembershipServiceвнутрішнє PersonService, ви або передаєте його PersonServiceконструктору, або додаєте властивість (getter та setter), щоб встановити його локальний примірник. Це означає, що вам PersonServiceбільше не потрібно турбуватися про те, як створити файл GroupMembershipService, він просто приймає ті, які йому надані, і працює з ними. Це також означає, що все, що є підкласом GroupMembershipServiceабо реалізує GroupMembershipServiceінтерфейс, може бути "введено" в PersonService, і PersonServiceне потрібно знати про зміни.


29
Було б чудово, якби ви могли навести той самий приклад коду ПІСЛЯ за допомогою DI
CodyBugstein

170

Прийнята відповідь хороша - але я хотів би додати до цього, що DI дуже схоже на класичне уникання твердо кодованих констант у коді.

Якщо ви використовуєте якусь константу, наприклад ім'я бази даних, ви швидко перемістите її зсередини коду в якийсь файл конфігурації і передасте змінну, що містить це значення, до місця, де це потрібно. Причиною цього є те, що ці константи зазвичай змінюються частіше, ніж решта коду. Наприклад, якщо ви хочете перевірити код у тестовій базі даних.

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


18
+1 "об'єкти змінюються частіше, ніж код, який їх використовує". Для узагальнення додайте непряму в точках потоку. Залежно від точки потоку, непрямі називають різними назвами !!
Четхан

139

Спробуємо простий приклад з класами « Автомобіль та двигун» , будь-який автомобіль потребує двигуна, щоб ходити куди-небудь, принаймні поки що. Отже нижче, як виглядатиме код без введення залежності.

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, тоді нам потрібно буде переписати автомобільний клас. І чим більше додаток, тим більше проблем і головного болю нам доведеться додати та використовувати новий тип двигуна.

Іншими словами, при такому підході, що наш клас автомобілів високого рівня залежить від нижнього рівня 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, а не від конкретної реалізації двигуна. Тепер єдиний трюк полягає в тому, як ми створимо екземпляр Автомобіля і надамо йому фактичний конкретний клас двигуна, як GasEngine або ElectricityEngine. Ось звідки надходить ін'єкційна залежність .

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

Тут ми в основному вводимо (передаємо) нашу залежність (екземпляр двигуна) конструктору автомобілів. Тож тепер у наших класах є слабке сполучення між об'єктами та їх залежностями, і ми можемо легко додавати нові типи двигунів, не змінюючи клас автомобілів.

Основна перевага ін'єкції залежності - це те, що класи більш вільно пов'язані, оскільки вони не мають жорстко кодованих залежностей. Це слідує принципу інверсії залежності, який був згаданий вище. Замість посилань на конкретні реалізації, класи запитують абстракції (зазвичай інтерфейси ), які надаються їм під час побудови класу.

Отже, врешті-решт, введення Dependency - це лише техніка досягнення слабкої зв'язку між об'єктами та їх залежностями. Замість того, щоб безпосередньо створювати залежності, необхідні класу для виконання своїх дій, залежності надаються класу (найчастіше) за допомогою конструкторської інжекції.

Крім того, коли у нас є багато залежностей, дуже корисно використовувати контейнери Інверсії управління (IoC), які ми можемо сказати, які інтерфейси слід відобразити, до яких конкретних реалізацій для всіх наших залежностей, і ми можемо це вирішити для нас, коли вони будуються наш об’єкт. Наприклад, ми могли б вказати в картографії для контейнера IoC, що залежність IEngine повинна бути віднесена до класу GasEngine, і коли ми запитаємо контейнер IoC для примірника нашого класу Car , він автоматично побудує наш клас автомобіля з залежністю GasEngine передав.

ОНОВЛЕННЯ: Нещодавно спостерігав за курсом EF Core від Джулі Лерман, а також сподобалось її коротке визначення про DI.

Ін'єкція залежності - це схема, яка дозволяє вашій програмі вводити об'єкти під час руху в класи, які їм потрібні, не примушуючи цих класів нести відповідальність за ці об'єкти. Це дозволяє вашому коду бути більш вільно пов'язаним, і Entity Framework Core підключається до цієї ж системи послуг.


2
просто з цікавості, чим це відрізняється від стратегії? Ця закономірність інкапсулює алгоритми та робить їх взаємозамінними. Складається враження, що введення залежності та структури стратегій дуже схожі.
еліксир

110

Давайте уявимо, що ви хочете порибалити:

  • Без ін'єкцій залежностей потрібно про все подбати самостійно. Потрібно знайти човен, придбати вудку, шукати приманку і т. Д. Це, звичайно, можливо, але це покладає на вас велику відповідальність. У програмному відношенні це означає, що вам потрібно виконати пошук усіх цих речей.

  • З ін'єкцією залежностей хтось інший дбає про всю підготовку та надає необхідне вам обладнання. Ви отримаєте («уколу») човен, вудку та приманку - все готове до використання.


59
Перегляньте, уявіть, ви наймаєте сантехніків, щоб переобладнати свою ванну кімнату, яка потім каже: "Чудово, ось список інструментів та матеріалів, які мені потрібні, щоб отримати для мене". Чи не повинна це бути робота сантехніка?
jscs

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

22
@JoshCaswell Ні, це було б роботою сантехніка. Як клієнт вам потрібна сантехніка. Для цього вам потрібен сантехнік. Сантехніку потрібні інструменти. Щоб отримати їх, вона обладнана водопровідною компанією. Як клієнт, ви не хочете точно знати, що робить або потребує сантехнік. Як сантехнік ви знаєте, що вам потрібно, але ви просто хочете виконувати свою роботу, а не отримуєте все. Як роботодавець сантехніків ви відповідаєте за обладнання сантехніків тим, що їм потрібно, перш ніж надсилати їх до будинків людей.
сара

@kai Я розумію вашу думку. У програмному забезпеченні ми говоримо про завод, правильно? Але DI також зазвичай означає, що клас не використовує фабрику, тому що досі не вводиться. Вам, замовнику, потрібно було б звернутися до роботодавця (фабрики), щоб надати вам інструменти, щоб ви могли перейти до сантехніка. Це не так, як це насправді працювало б у програмі? Тому, хоча замовник (виклик класу / функції / що завгодно) не повинен закуповувати інструменти, він все ще повинен бути середнім чоловіком, щоб переконатися, що він перейшов до сантехніка (класу впорскування) від роботодавця (заводу).
KingOfAllTrades

1
@KingOfAllTrades: Звичайно, у якийсь момент у вас має бути хтось, хто працює і облаштовує сантехніків, або у вас немає сантехніків. Але у вас немає замовника, який це робить. Клієнт просто просить у сантехніка і отримує вже обладнаний тим, що йому потрібно для своєї роботи. З DI, у вас все-таки є якийсь код, щоб виконати залежності. Але ви відокремлюєте його від коду, який справді працює. Якщо ви приймаєте це в повній мірі, ваші об'єкти просто роблять відомі свої залежності, і побудова об'єктів-графіків відбувається зовні, часто в коді init.
cHao

102

Це найпростіше пояснення про Dependency Injection і Dependency Injection Container я коли- небудь бачив:

Без введення залежностей

  • Додаток потребує Foo (наприклад, контролер), так:
  • Додаток створює Foo
  • Додаток викликає Foo
    • Foo потрібен бар (наприклад, послуга), так що:
    • Foo створює Бар
    • Фу дзвонить Бар
      • Бару потрібен Bim (сервіс, сховище, ...), так що:
      • Бар створює Бім
      • Бар щось робить

З ін'єкцією в залежність

  • Додаток потребує Foo, який потребує Bar, якому потрібен Bim, так що:
  • Додаток створює Bim
  • Додаток створює Бар і надає йому Бім
  • Додаток створює Foo і надає йому бар
  • Додаток викликає Foo
    • Фу дзвонить Бар
      • Бар щось робить

Використання контейнера для ін'єкційних залежностей

  • Додаток потрібно Foo так:
  • Додаток отримує Foo від контейнера, тому:
    • Контейнер створює Бім
    • Контейнер створює бар і дає йому Бім
    • Контейнер створює Foo і надає йому Bar
  • Додаток викликає Foo
    • Фу дзвонить Бар
      • Бар щось робить

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 -> PermanentBulb // перемикач безпосередньо підключений до постійної лампочки, тестування неможливе легко

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

З ін'єкцією в залежність

Перемикач знає лише, що мені потрібно вмикати / вимикати залежно від того, яку лампочку передано мені. Тому,

Перемикач -> Лампа1 АБО Лампа2 АБО Нічна лампочка (введена залежність)

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(); 
    ... 
  } 
}`

36

Що таке впорскування в залежність (DI)?

Як говорили інші, введення залежної ін'єкції (DI) знімає відповідальність за безпосереднє створення та управління тривалістю життя інших об'єктів, від яких залежить наш клас інтересів (споживчий клас) (у розумінні UML ). Ці екземпляри замість цього передаються до нашого споживчого класу, як правило, в якості параметрів конструктора або через встановлення властивостей (управління об'єктом залежності та перехід до споживчого класу зазвичай виконується контейнером інверсії управління (IoC) , але це інша тема) .

DI, DIP і твердий

В Зокрема, в парадигмі Роберта C Мартіна ТВЕРДИХ принципів об'єктно - орієнтованого дизайну , 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()`"

Враховуючи, що після застосування ПДІ вимога є послабленою, а проблема отримання та управління тривалістю 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 вони працюють безшовно на задньому плані, що дозволяє кодеру зосередити увагу на коді, а не турбуватися про залежності.

Ключ до дружнього коду - уникати статичного з’єднання класів, а також не використовувати новий () для створення залежностей

Згідно з наведеним вище прикладом, розв'язка залежностей вимагає певних зусиль із проектування, і для розробника існує зміна парадигми, необхідна для того, щоб зруйнувати звичку newбезпосередньо використовувати залежності, а замість цього довіряти контейнеру для управління залежностями.

Але переваг багато, особливо в можливості ретельно перевірити свій клас інтересів.

Примітка : Створення / відображення / проекція (через new ..()) POCO / POJO / Серіалізація DTO / Entity Graphs / Anonymous JSON projections та ін. - тобто класи "Дані", які використовуються або повертаються з методів, не розглядаються як залежності (у Сенс UML) і не підлягає DI. Використовувати newдля проектування це просто чудово.


1
Проблема - DIP! = DI. DIP стосується від'єднання абстракції від реалізації: А. Модулі високого рівня не повинні залежати від модулів низького рівня. Обидва повинні залежати від абстракцій. B. Абстракції не повинні залежати від деталей. Деталі повинні залежати від абстракцій. DI - це спосіб від'єднати створення об'єкта від використання об'єкта.
Рікардо Рівальдо

Так, різниця чітко прописана в моєму пункті 2, "DI одна з можливих реалізацій DIP" , у парадигмі SOLID дядька Боба. Я також це зрозумів у попередньому дописі.
StuartLC

25

Вся суть введення залежності (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 століття.

Наведене вище зображення - це зображення портативного магнітофона котушки до котушки, середина 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();
}

З ін'єкцією в залежність інжектор залежностей зніме для нас інстанцію

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, тому ідея полягає в тому, щоб від'єднати ці об'єкти один від одного. Нам не потрібно сильно кодувати об'єкт, використовуючи нове ключове слово, а не ділити залежність для об'єктів під час виконання, незважаючи на час компіляції. Якщо говорити про

Як працює впорскування залежно навесні:

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

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 наближає вас до принципу єдиної відповідальності (SR), як і surgeon who can concentrate on surgery.

Коли використовувати DI: я ​​б рекомендував використовувати DI майже у всіх виробничих проектах (малих / великих), особливо у постійно мінливих бізнес-середовищах :)

Чому: Тому що ви хочете, щоб ваш код був легко перевіряється, макерувався і т. Д., Щоб ви могли швидко протестувати свої зміни та перенести їх на ринок. Крім того, чому б вам не було, коли у вас є безліч дивовижних безкоштовних інструментів / рамок, які допоможуть вам у вашій подорожі до кодової бази, де ви маєте більше контролю.


@WindRider Дякую Я не можу погодитися більше. Людське життя та людське тіло - це чудові приклади досконалості дизайну. Хребет є прекрасним прикладом ESB:) ...
Anwar Husain

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) Використовуйте бібліотеку: для кінця Android2

Переваги

  • Зробіть тест простіше
  • Коли ви змінюєте Service, вам потрібно змінити його лише у класі інжектор
  • Якщо ви використовуєте use Constructor Injection, дивлячись на конструктор Client, ви побачите, скільки залежить Clientклас

Недоліки

  • Якщо ви використовуєте використання 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 є нульовим. Це підкреслює важливість отримання дійсної залежності. На мою думку, конструкторська ін'єкція є кращим механізмом надання об'єкту його залежностей. Розробнику зрозуміло, посилаючись на об'єкт, які залежності потрібно надати об'єкту "Особа" для належного виконання.

Ін'єкція сетера

Але розглянемо наступний приклад… Припустимо, у вас клас з десятьма методами, які не мають залежностей, але ви додаєте новий метод, який має залежність від IDAO. Ви можете змінити конструктор на використання конструкторської ін'єкції, але це може змусити вас змінити всі виклики конструктора в усьому місці. Крім того, ви можете просто додати новий конструктор, який приймає залежність, але тоді як розробник легко знає, коли використовувати один конструктор над іншим. Нарешті, якщо залежність створити дуже дорого, чому її слід створювати та передавати конструктору, коли вона може використовуватися лише рідко? "Ін'єкція сетера" - ще одна техніка 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 AClass не вводиться, чи важче це знущатися?
  5. Повертаючись до першого питання. Якщо використання нового об'єкта () погано, то чому ми вводимо реалізацію, а не інтерфейс? Я думаю, що багато з вас говорять, що ми насправді вводимо інтерфейс, але конфігурація змушує вас вказати реалізацію цього інтерфейсу .. ні під час виконання .. він жорстко кодується під час компіляції.

Це ґрунтується на опублікованій відповіді @Adam N.

Чому PersonService більше не має турбуватися про GroupMembershipService? Ви щойно згадали GroupMembership має багато речей (об'єктів / властивостей), від яких залежить. Якщо в PService потрібна GMService, ви матимете її як властивість. Ви можете знущатися над цим незалежно від того, ввели ви його чи ні. Єдиний раз, коли я хотів би, щоб його вводили, якщо в GMService були більш конкретні класи дітей, про які ви не знали б до часу виконання. Тоді ви хочете вставити підклас. Або якщо ви хочете використовувати це як однотонний або прототип. Якщо чесно, у конфігураційному файлі є все жорстке кодування, що стосується того, який підклас для типу (інтерфейсу) він збирається вводити під час компіляції.

EDIT

Приємний коментар Жозе Марії Арранц щодо DI

DI збільшує згуртованість, усуваючи будь-яку потребу визначити напрямок залежності і написати будь-який клей-код.

Помилковий. Напрямок залежностей у формі XML або як анотації, ваші залежності записуються як код XML та анотації. XML та примітки є вихідним кодом.

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

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

Про замінний: за допомогою дуже простого архіву .properties та Class.forName ви можете визначити, які класи можуть змінюватися. Якщо будь-який клас вашого коду можна змінити, Java не для вас, використовуйте мову сценаріїв. До речі: анотації неможливо змінити без перекомпіляції.

На мою думку, є одна єдина причина каркасів: зменшення плит котла. За допомогою добре виконаної фабричної системи ви можете зробити те ж саме, більш контрольоване та більш передбачуване, як і ваш бажаний фреймворк DI, рамки DI обіцяють зменшення коду (XML та анотації теж вихідний код). Проблема полягає в тому, що це зменшення плит котла реально в дуже простих випадках (один екземпляр на клас і подібне), іноді в реальному світі вибір відповідного об’єкта обслуговування не такий простий, як зіставлення класу на однотонний об'єкт.


8

Популярні відповіді не допомагають, оскільки вони визначають введення залежності таким чином, який не є корисним. Погодимось, що під "залежністю" ми маємо на увазі деякий раніше існуючий інший об'єкт, який потрібен нашому об'єкту X. Але ми не кажемо, що робимо "ін'єкцію залежності", коли говоримо

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

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

"Введення залежності" вважається типом "інверсії управління", що означає, що якась логіка виймається з абонента. Це не той випадок, коли абонент передає параметри, тому, якби це було DI, DI не означало б інверсію управління.

DI означає, що між абонентом та конструктором є проміжний рівень, який управляє залежностями. Makefile - простий приклад введення залежності. "Зателефонуючий" - це людина, яка вводить "make bar" у командному рядку, а "конструктор" - компілятор. Makefile визначає, що смуга залежить від foo, і це робить a

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

перш ніж робити а

gcc foo.o bar.o -o 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. Повинна бути прийнята відповідь.
Marche Remi

4

від Book Apress.Spring.Persistence.with.Hibernate.Oct.2010

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


4

Dependency Injection (DI) - це одна з шаблонів дизайну, яка використовує основну особливість OOP - взаємозв'язок одного об'єкта з іншим об'єктом. У той час як спадкування успадковує один об'єкт, щоб зробити більш складним і конкретизованим інший об'єкт, зв’язок або асоціація просто створює вказівник на інший об'єкт з одного об’єкта за допомогою атрибута. Потужність DI поєднується з іншими особливостями OOP, як інтерфейси та прихований код. Припустимо, у нас у бібліотеці є клієнт (абонент), який для простоти може позичити лише одну книгу.

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

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 так тісно пов'язаний з Інверсією управління (IoC), що означає, що наша програма передає управління іншому запущеному модулю, який робить ін'єкції бобів до нашого коду. (Кожен об'єкт, який може бути введений, може бути підписаний або розглянутий як Bean.) Наприклад, навесні це робиться шляхом створення та ініціалізації ApplicationContextконтейнер, який робить це для нас. Ми просто в своєму коді створюємо контекст і викликаємо ініціалізацію бобів. У цей момент ін'єкцію робили автоматично.


4

Ін'єкційна залежність для 5-річних дітей.

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

Що ви повинні робити, це заявити про необхідність: "Мені потрібно щось випити за обідом", і тоді ми переконаємося, що у вас є щось, коли ви сідете їсти.


1
Це однозначно відповідь батьків. ;)
Marche Remi

4

З Крістофера Норінга, книга Пабло Ділмана "Навчання кутовим - друге видання":

«Оскільки наші додатки ростуть і розвивається, кожен з наших кодових сутностей буде внутрішньо вимагати примірників інших об'єктів , які є більш відомими як залежності в світі програмної інженерії. Дія проходження таких залежностей залежного клієнта відомо як ін'єкції , і це також передбачає участь іншого суб'єкта коду, який називається інжектором . Інжектор візьме на себе відповідальність за інстанціювання та завантаження необхідних залежностейтому вони готові до використання з того моменту, коли вони успішно вводяться у клієнта. Це дуже важливо , тому що клієнт не знає нічого про те , як створити екземпляр своїх залежностей і знає тільки з інтерфейсу вони реалізують для того , щоб використовувати їх «.

Від: Антон Мойсеєв. книга "Кутова розробка з машинописом, друге видання.":

"Коротше кажучи, DI допомагає вам писати код у зв'язаному вигляді і робить ваш код більш перевіреним та багаторазовим ".


3

Простими словами, введення залежності (DI) - це спосіб усунення залежностей або тісної зв'язку між різними об'єктами. Ін'єкційна залежність дає згуртовану поведінку кожному об'єкту.

DI - це реалізація директора МОК Spring, який говорить: "Не дзвоніть нам, ми зателефонуємо вам". Використовуючи програміст ін'єкцій залежностей, не потрібно створювати об'єкт за допомогою нового ключового слова.

Об'єкти один раз завантажуються у контейнер Spring, а потім ми використовуємо їх повторно, коли нам це потрібно, отримуючи ці об'єкти з Spring контейнера методом getBean (String beanName).


3

Ін'єкція залежностей є основою концепції, пов’язаної з Spring Framework. Якщо створення рамки будь-якого проекту весна може виконувати життєво важливу роль, і тут ін'єкція залежності входить у глечик.

Насправді, припустимо, у java ви створили два різні класи, як клас A і клас B, і яку б функцію ви не мали в класі B, яку ви хочете використовувати в класі A, тож у цей час можна використовувати ін'єкцію залежності. де ви можете створити об'єкт одного класу в іншому, таким же чином ви можете вставити весь клас в інший клас, щоб зробити його доступним. цим способом залежність можна подолати.

ІНДЕКЦІЯ ВІДПОВІДЬНОСТІ ПРОСТО СКЛЮЧЕННЯ ДВОХ КЛАСІВ І НА ТОЖИЙ ЧАС ЗДІЙСНЯЄТЬСЯ ЇХ СЕПАРАТ.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.