Питання Що таке ін'єкція залежності?


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

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


2635
2017-09-25 00:28


походження


Див мою дискусію про ін'єкцію залежності Ось тут. - Kevin S.
Я згоден з коментарями щодо посилань. Я можу зрозуміти, що хочете послати когось іншого. Але принаймні додайте, чому ви зв'язуєте їх і що робить цю посилання кращою, ніж інші посилання я міг отримати, використовуючи google - Christian Payne
@AR: технічно залежність ін'єкції є ні особлива форма IoC. Швидше за все, IoC - це один із методів, який використовується для ін'єкції залежностей. Інші методи можуть бути використані для ін'єкції залежностей (хоча IoC є єдиним у звичайному використанні), а IoC використовується також для багатьох інших проблем. - Sean Reilly
Якщо ви не підсумовуєте це, ми не знаємо, що варто зупинитися, щоб прочитати його - Casebash
Що стосується посилань, пам'ятайте, що вони часто зникають так чи інакше. Існує зростаюча кількість мертвих посилань на SO відповідей. Отже, незалежно від того, наскільки добре пов'язана стаття, це взагалі не добре, якщо ви не можете знайти його. - DOK


Відповіді:


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

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

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

Це може бути складно, коли всі, що ви хочете зробити, це запустити деякі тестування UnitClass, особливо якщо myObject - це те, що робить складний диск або доступ до мережі. Отже, ви дивитесь на насміхання мого об'єкта, але також якось перехоплює фабричний дзвінок. Жорсткий Замість цього передайте об'єкт як аргумент конструктору. Тепер ви перемістили проблему в інше місце, але тестування може стати набагато легшим. Просто створіть макет myObject і перепустіть це. Конструктор тепер виглядає трохи схожим:

public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

Це один стиль ін'єкції залежності - через конструктор. Можливі декілька механізмів.

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

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

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


1638
2017-09-25 00:49



Визнаючи, що стаття Бена Гофштайна стосовно статті Мартіна Фаулера є необхідною, оскільки вказує на "обов'язково читати" на цю тему, я приймаю відповідь WDS, оскільки насправді він відповідає на це питання на ЗП. - AR.
+1 для пояснення та мотивації: створюючи об'єкти, на яких клас залежить від чужої проблеми. Інший спосіб сказати, що DI робить заняття більш послідовними (вони мають менше обов'язків). - Fuhrmanator
Ви кажете, що залежність передана "в конструктору", але, як я розумію, це не є суворо істинним. Це все-таки ін'єкція залежності, якщо залежність встановлюється як властивість після того, як об'єкт був інстанцією, правильно? - Mike Vella
@MikeVella Так, це правильно. У більшості випадків це не робить реальних змін, хоча властивості, як правило, дещо гнучкіші. Я трохи відредагую текст, щоб вказати на це. - wds
Одне з найкращих відповідей, який я знайшов до цього часу, таким чином, я дійсно зацікавлений у його поліпшенні. Немає опису третьої форми ін'єкції залежностей: Інтерфейс ін'єкції. - Marc Dix


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

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

є стаття Мартіна Фаулера що також може виявитися корисним.

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

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


2063
2017-09-26 16:50



Мені подобається пояснення статті Джеймса, особливо кінця: "Тим не менш, ви повинні дивувати будь-який підхід, який приймає три поняття (" TripPlanner "," CabAgency "та" AirlineAgency "), перетворює їх у класи з дев'ятьма плюсами, а потім додає десятки рядків коду клею та XML-конфігурації, перш ніж буде написано одну лінію логіки додатків ". Це те, що я бачив дуже часто (на жаль), - те, що ін'єкції залежності (що є добре як таке, як він пояснив), неправильно використовується для того, щоб надто ускладнити те, що можна було зробити легше - закінчити створення "підтримки" коду ... - Matt
Re: "Інвентаризація та передача об'єктів (залежностей) явним чином є настільки ж хорошою ін'єкцією, як ін'єкція за рамками". Отже, чому люди зробили це? - dzieciou
З тієї ж причини, що кожна структура отримує (або, принаймні, повинен отримати) написана: тому що є багато повторюваних / кодів коду, які потрібно написати, коли ви досягнете певної складності. Проблема полягає в тому, що багато людей досягатимуть рамки навіть тоді, коли це не є суто необхідним. - Thiago Arrais
Це повинно бути правильною відповіддю ... або "Встановлення залежності означає" проходження посилання на об'єкт " - Hal50000
25-денний термін для 5-відсоткової концепції мертвий. Ось хороша стаття, яка мені допомогла: codeproject.com/Articles/615139/... - Hill


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

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

Наприклад, розгляньте a Car об'єкт

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

Без ін'єкції залежності (ДІ):

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

Джерело: Розуміння залежності введення


507
2018-05-22 04:01



Таким чином, я розумію це, замість того, щоб екзаменувати новий об'єкт як частину іншого об'єкта, ми можемо ввести цей об'єкт, коли і коли це потрібно, таким чином, видаляючи з нього залежність першого об'єкта. Це так? - JeliBeanMachine
Я описав це з прикладом кав'ярні тут:digigene.com/design-patterns/dependency-injection-coffeeshop - Ali Nem
Схоже на цю аналогію, тому що це просто англійська, використовуючи просту аналогію. Скажімо, я Toyota, вже витративши занадто багато фінансово та енергетичної сили на виробництво автомобіля від дизайну до згортання лінії збірки, якщо існують авторитетні виробники шин, чому я повинен починати з нуля, щоб розподілити виробництво шин, тобто new шина? Я не. Все, що я повинен зробити, це купувати (ін'єкційувати через параметр) від них, встановити і wah-lah! Отже, повертаючись до програмування, скажімо, що проект C # повинен використовувати існуючу бібліотеку / клас, існує два способи запуску / налагодження, 1 - додавання посилання на весь проект цього - Jeb50
(con't), .. зовнішня бібліотека / клас або 2-додавайте її з DLL. Якщо ми не повинні бачити, що знаходиться в цьому зовнішньому класі, додавання його як DLL - це простий спосіб. Таким чином, варіант 1 має бути new це, варіант 2 пропускати його як парам. Може бути не точним, але простим дурним легко зрозуміти. - Jeb50
Дивовижне пояснення - Avan


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

Наприклад, розглянемо ці класи:

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 має декілька речей, які вимагає, або, що ще гірше, існують ініціалізатори "setters", які потрібно викликати в GroupMembershipService, код швидко зростає, а також PersonServiceтепер залежить не тільки від GroupMembershipService але і все інше GroupMembershipService залежить від. Крім того, зв'язок з GroupMembershipService жорстко закодований в PersonService а це означає, що ви не можете "придушити" a GroupMembershipService для тестування, або використовувати шаблон стратегії в різних частинах вашої програми.

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


233
2017-09-25 06:49



Було б чудово, якщо б ви могли навести такий же код, як після використання DI - CodyBugstein
Я б хотів, щоб у пітона було щось подібне. Зараз ми повинні здивувати все тестування. - user2601010
Ви, звичайно, можете робити Залежність ін'єкції з Python, але багато великих бібліотек (Django і т.д.) не тому, що сміливі бібліотеки в python настільки тверді. Вам все одно доводиться пихати, коли ви використовуєте DI, це просто легше. - Adam Ness


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

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

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


142
2018-01-06 18:33



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


Давайте уявити, що хочеш піти на риболовлю:

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

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


96
2017-10-22 04:47



Зверніть увагу на те, що ви наймаєте сантехнік, щоб переробити вашу ванну кімнату, а потім каже: "Відмінно, ось список тих інструментів і матеріалів, які мені потрібні, щоб отримати мене". Чи не повинно бути робота сантехніків? - Josh Caswell
Так що хтось повинен піклуватися про якусь особу, якої не знає бізнес .., але все ж вирішує зібрати список човна, палички та приманки - хоча і готовий до використання. - Chookoos
@JoshCaswell Ні, це буде робота emplyer сантехнік. Як клієнт, вам потрібна сантехніка. Для цього потрібен сантехнік. Для роботи сантехніків необхідні інструменти. Щоб отримати ці, він оснащений сантехнікою компанії. Як клієнт, ви не бажаєте точно знати, що робить або потрібно сантехнік. Як сантехнік ви знаєте, що вам потрібно, але ви просто хочете виконувати свою роботу, не отримаєте все. Як роботодавець сантехніків ви несете відповідальність за оснащення ваших сантехніків тим, що їм потрібно, перш ніж відправити їх до будинків людей. - kai
@кі я розумію вашу точку зору. У програмі, про яку ми говоримо про фабрику, правильно? Але DI також зазвичай означає, що клас не використовує фабрику, оскільки він все ще не вводиться. Вам, замовнику, доведеться звернутися до роботодавця (заводу), щоб надати вам інструменти, щоб ви могли перейти до сантехніків. Хіба це не так, як воно фактично працюватиме в програмі? Таким чином, поки замовник (клас зі службою / функція / щось) не повинен придбати інструменти, їм все одно повинен бути середній чоловік, щоб він доставив його сантехніку (введеному класу) від роботодавця (фабрики). - KingOfAllTrades
@ KingOfAllTrades: Звичайно, в якийсь момент вам доведеться мати когось, хто працює та облаштовує сантехніків, або у вас немає сантехніків. Але у вас немає клієнта, який це робить. Клієнт просто просить сантехнік, і він одержує один вже обладнаний тим, що йому потрібно для виконання своєї роботи. За допомогою DI ви все ще маєте певний код для виконання залежностей. Але ви розділяєте його з коду, який робить справжню роботу. Якщо ви візьмете це в повній мірі, ваші об'єкти просто повідомлять про свої залежності, а побудова об'єктів-графів відбувається назовні, часто в коді init. - cHao


Це це найпростіше пояснення про Залежність ін'єкції і Залежність ін'єкційного контейнера Я коли-небудь бачив:

Без ін'єкції залежності

  • Програма потребує Foo (наприклад, контролер), так що:
  • Програма створює Foo
  • Заява викликає Foo
    • Foo потребує Бар (наприклад, послуга), так що:
    • Foo створює бар
    • Foo називає Bar
      • Бар потрібно Бім (служба, репозиторій, …), так:
      • Бар створює Бім
      • Бар робить щось

Залежність ін'єкцій

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

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

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

Залежність ін'єкції і залежність ін'єкційних контейнерів різні речі:

  • Залежність ін'єкцій - це метод написання кращого коду
  • DI Container - це інструмент, який допомагає ін'єкційним залежності

Вам не потрібен контейнер для ін'єкції залежності. Однак контейнер може вам допомогти.


79
2018-05-05 11:53



@Trix Чи добре використовувати ін'єкції залежності чи ні? - roottraveller
@rootTraveller google твій друг: Коли не підходить використання шаблону ін'єкцій залежностей? - Trix


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

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

Зараз наш клас автомобіля залежить тільки від IEngine інтерфейсу, а не конкретної реалізації двигуна. Тепер єдиним способом є те, як ми створюємо екземпляр автомобіля і даємо йому конкретний конкретний двигун класу, як GasEngine або ElectricityEngine. Ось де Залежність ін'єкції входить

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

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

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

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

Також, коли у нас багато залежностей, дуже корисною є практика використання контейнерів Inversion of Control (IoC), які ми можемо визначити, які інтерфейси повинні бути зіставлені з конкретними реалізаціями для всіх наших залежностей, і ми можемо вирішити ці залежності для нас, коли він будує наш об'єкт. Наприклад, ми можемо вказати у відображеннях контейнера IoC, що IEngine залежність повинна бути відображена на GasEngine класу і коли ми запитуємо контейнер IoC для екземпляра нашого Автомобіль клас, він буде автоматично будувати наш Автомобіль клас з a GasEngine залежність пройшла в

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

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


77
2017-07-06 09:42



гарна пояснення, чому у цього немає голосів? - nhoxbypass
Це заслуговує бути одним з найпопулярніших відповідей. - Shwetabh Shekhar
просто з цікавості, як це відрізняється від стратегії? Цей шаблон являє собою інкапсуляцію алгоритмів і їх взаємозамінність. Це відчуває себе як залежність ін'єкцій і стратегія моделей дуже схожі. - elixir


Чи не "ін'єкції залежності" просто означають використання параметризованих конструкторів та публічних установок?

У статті Джеймса Шор показує наступні приклади для порівняння.

Конструктор без вживання залежності:

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

45
2018-05-02 00:40



Звичайно, у версії DI ви не хочете ініціалізувати об'єкт myDatabase у конструкторі аргументів немає? Тут здається ніякого сенсу, і це може призвести до виключення, якщо ви спробували називати DoStuff без виклику перевантаженого конструктора? - Matt Wilko
Лише якщо new DatabaseThingie() не генерує дійсний примірник myDatabase. - JaneGoodall


Що таке ін'єкція залежності (ДІ)?

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

DI, DIP і SOLID

Зокрема, в парадигмі Роберта С. Мартіна SOLID принципи об'єктно-орієнтованого дизайну, DI є однією з можливих реалізацій Принцип інверсії залежностей (DIP). The DIP є D з SOLID мантра  - інші реалізації DIP включають в себе Locator Locator та шаблони плагінів.

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

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

Це можна розглядати по-різному:

  • Якщо період збереження життєвого циклу залежностей класом споживача повинен бути збережений, контроль може бути відновлений шляхом ін'єкції (абстрактної) фабрики для створення екземплярів класу залежностей у споживчий клас. Споживач зможе отримати екземпляри через 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 до абстракції, такі як a TextWriter. Залежність ін'єкції, як правило, реалізується як constructor ін'єкція (передача абстракції на залежність як параметр конструктору класу споживача) або Setter Injection (проходження залежності через a setXyz() Setter або .Net Property з {set;} визначений). Конструктор ін'єкцій є кращим, оскільки це гарантує, що клас буде в правильному стані після побудови, і дозволяє полями внутрішніх залежностей позначати як readonly (C #) або final (Java). Отже, використовуючи ін'єкцію конструктора на наведеному вище прикладі, це залишає нас:

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

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

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

(Бетон Clock потребує надання, що, звичайно, може повернутися до DateTime.Now, і дві залежності повинні бути забезпечені контейнером IoC через ін'єкцію конструктора)

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

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

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

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

Наступні кроки

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

  • відображення між кожною абстракцією та налагодженою конкретною реалізацією (наприклад, "будь-коли споживач вимагає IBar, повернення а ConcreteBar приклад ")
  • Політика може бути встановлена ​​для керування кожною залежністю, наприклад, щоб створити новий об'єкт для кожної споживчої екземпляри, поділитися екземпляром одностадійної прив'язки для всіх споживачів, поділитися тими самими примірниками залежностей тільки з однією темою і т. д.
  • В .Net контейнери IoC знають про протоколи, такі як IDisposable і візьме на себе відповідальність Disposing залежностей з налагодженим керуванням життєвим циклом.

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

Ключ до коду DI-friendly полягає в тому, щоб уникнути статичної взаємодії класів, а не використовувати новий () для створення залежностей

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

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

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


34
2018-04-28 20:17



Проблема DIP! = DI. DIP стосується відключення абстракції від реалізації: A. Модулі високого рівня не повинні залежати від модулів низького рівня. Обидва повинні залежати від абстракцій. Б. Абстракції не повинні залежати від подробиць. Деталі повинні залежати від абстракцій. DI - спосіб відокремити створення об'єкта від використання об'єкта. - Ricardo Rivaldo
Так, різниця чітко викладена в моєму пункті 2 "DI однієї з можливих реалізацій DIP", в парадигмі SOLID Дядька Боба. Я також зробив це ясно в більш ранньому пості. - StuartLC