Критика та недоліки ін'єкції залежності


119

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

  • Можливість / простота ізоляції при тестуванні одиниць
  • Явно визначають залежності класу
  • Сприяння гарному дизайну (наприклад, принцип єдиної відповідальності (SRP))
  • Увімкнення швидкого переключення реалізацій ( DbLoggerзамість ConsoleLoggerнаприклад)

Я вважаю, що існує загальна думка про галузь, що DI - це корисна модель. Наразі критики не надто багато. Недоліки, про які йдеться у громаді, зазвичай незначні. Дехто з них:

  • Збільшена кількість занять
  • Створення непотрібних інтерфейсів

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

Я хотів би запитати:

  • Чи слід використовувати ін'єкцію залежності, коли ми маємо лише одну реалізацію?
  • Чи слід забороняти створювати нові об'єкти, окрім мовних / рамкових?
  • Чи є введення однієї реалізації поганою ідеєю (скажімо, у нас є лише одна реалізація, тому ми не хочемо створювати "порожній" інтерфейс), якщо ми не плануємо одиничне тестування певного класу?

33
Ви справді запитуєте про ін'єкцію залежності як зразок, або ви питаєте про використання рамки DI? Це дійсно відмінні речі, ви повинні уточнити, яка частина проблеми вас цікавить, або прямо запитати про обидва.
Фракс

10
@Frax про шаблон, а не рамки
Landeeyo

10
Ви плутаєте інверсію залежності з введенням залежності. Перший - принцип дизайну. Останнє - це техніка (звичайно реалізована з наявним інструментом) для побудови ієрархій об’єктів.
jpmc26

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

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

Відповіді:


160

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

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

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

Через більш ніж 2 людей, які мають плутанину щодо терміну батько та дитина, в контексті введення залежності:

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

Ін'єкційна залежність є схемою для складу об'єкта .

Чому інтерфейси?

Інтерфейси - це договір. Вони існують для обмеження того, наскільки щільно пов'язані два об'єкти. Не кожній залежності потрібен інтерфейс, але вони допомагають писати модульний код.

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

Чому рамки?

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

  • Автомобільна залежність до компонентів
  • Налаштування компонентів за допомогою певних налаштувань
  • Автоматизація коду пластини котла, щоб вам не довелося бачити його написаним у кількох місцях.

Вони також мають такі недоліки:

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

Все, що говорили, є компроміси. Для невеликих проектів, де не так багато рухомих деталей, і мало підстав використовувати рамку DI. Однак для складніших проектів, де вже створені певні компоненти, рамки можуть бути виправдані.

А як щодо [випадкової статті в Інтернеті]?

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

Словом, подумайте самі і спробуйте все.

Робота зі "старими головами"

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

Я мав честь працювати з декількома з них, і вони можуть надати брутально чесні відгуки, які мають багато сенсу. І там, де вони бачать цінність, вони додають ці інструменти до свого репертуару.


6
@CarlLeth, я працював з низкою фреймворків від варіантів Spring до .net. Весна дозволить вам вводити реалізацію прямо в приватні поля, використовуючи певну магію відображення / класного завантажувача. Єдиний спосіб перевірити складені такі компоненти - це використовувати контейнер. Весна має бігуни JUnit для налаштування тестового середовища, але це складніше, ніж налаштовувати речі самостійно. Так так, я просто наводив практичний приклад.
Берин Лорич

17
Є ще один недолік, який я вважаю перешкодою для DI через рамки, коли я одягаю капелюшок для усунення несправностей / технічного обслуговування: моторошна дія на відстані, яку вони надають, ускладнює налагодження в режимі офлайн важче. У гіршому випадку я повинен запустити код, щоб побачити, як ініціалізуються і передаються залежності. Ви згадуєте про це в контексті "тестування", але насправді набагато гірше, якщо ви тільки починаєте дивитися на джерело, ніколи розум намагається змусити його запустити (що може включати тонни налаштування). Перешкоджаючи моїй здатності розповідати, що робить код, просто поглянувши на нього, це погана річ.
Єроен Мостерт

1
Інтерфейси не є контрактами, вони просто API. Контракти передбачають семантику. У цій відповіді використовується специфічна для мови термінологія та специфікації Java / C #.
Френк Хілеман

2
@BerinLoritsch Основним моментом вашої власної відповіді є те, що принцип DI! = Будь-яка задана рамка DI. Те, що весна може робити жахливі, непрощенні речі, є недоліком Весни, а не рамки DI взагалі. Хороша рамка DI допомагає вам слідувати принципу DI без противних хитрощів.
Карл Лет

1
@CarlLeth: всі рамки DI розроблені для видалення або автоматизації деяких речей, які програміст не бажає пояснювати, вони просто залежать від способу. Наскільки мені відомо, усі вони знімають здатність знати, як (чи якщо ) взаємодіють класи A і B, просто дивлячись на A і B - принаймні, вам також потрібно переглянути налаштування DI / конфігурацію / конвенції. Не проблема для програміста (це саме те, що вони хочуть, насправді), а потенційна проблема для технічного обслуговування / налагоджувача (можливо того самого програміста, пізніше). Це угода, яку ви робите, навіть якщо ваша рамка DI "ідеальна".
Єроен Мостерт

88

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

Спершу подумайте, чи зможете ви зменшити або усунути залежності. За інших рівних умов ми хочемо, щоб кожен компонент в системі мав якомога менше залежностей. І якщо залежностей не буде, питання впорскувати чи не стає суперечливим!

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

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

Але набагато кращим рішенням є просто відокремити аналіз від введення / виводу. Якщо аналіз буде вилучений в модуль без побічних ефектів, це буде набагато простіше перевірити. Зауважте, що глузування - це кодовий запах - цього не завжди можна уникнути, але загалом, краще, якщо ви можете протестувати, не покладаючись на глузування. Таким чином, усуваючи залежності, ви уникаєте проблем, які DI повинен усунути. Зауважте, що така конструкція також набагато краще дотримується SRP.

Хочу підкреслити, що DI не обов'язково полегшує SRP або інші хороші принципи проектування, такі як розділення проблем, висока згуртованість / низька зв'язок тощо. Це може також мати протилежний ефект. Розглянемо клас A, який використовує інший клас B внутрішньо. B використовується тільки A і тому повністю інкапсульований і може вважатися деталлю реалізації. Якщо ви зміните це, щоб ввести B в конструктор A, то ви відкрили цю детальну інформацію про реалізацію, і тепер знання про цю залежність і про те, як ініціалізувати B, термін експлуатації B і так далі повинні існувати в іншому місці в системі окремо від А. Отже, у вас загалом гірша архітектура з протікаючими проблемами.

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

Проблема полягає в тому, коли візерунки та архітектури самі стають цілями, а не інструментами. Просто запитуючи "Чи слід використовувати DI?" це своєрідне ставлення воза перед конем. Ви повинні запитати: "У нас є проблеми?" і "Яке найкраще рішення для цієї проблеми?"

Частина вашого запитання зводиться до: "Чи слід створювати зайві інтерфейси, щоб задовольнити вимоги шаблону?" Ви, напевно, вже усвідомлюєте відповідь на це - абсолютно ні ! Кожен, хто каже вам інакше, намагається продати вам щось - швидше за все, дорогі консультаційні години. Інтерфейс має значення лише в тому випадку, якщо він представляє абстракцію. Інтерфейс, який просто імітує поверхню одного класу, називається "інтерфейсом заголовка", і це відомий антипатерн.


15
Я не міг більше погодитися! Також зауважте, що глузування з речей заради цього означає, що ми насправді не тестуємо реальні реалізації. Якщо Aвикористовується Bу виробництві, але протестовано лише MockB, наші тести не говорять нам, чи буде він працювати у виробництві. Коли чисті (без побічних ефектів) компоненти доменної моделі впорскують і знущаються одне з одним, в результаті це величезна витрата часу кожного, роздута і тендітна база коду та низька впевненість у отриманій системі. Знущайтеся над межами системи, а не між довільними фрагментами тієї ж системи.
Варбо

17
@CarlLeth Чому ви вважаєте, що DI робить код "перевіреним та ремонтопридатним", а код без нього меншим? ЖакБ має рацію, що побічні ефекти - це те, що шкодить передбачуваності / ремонту . Якщо код не має побічних ефектів, нас не хвилює, що / де / коли / як він називає інший код; ми можемо тримати це просто і прямо. Якщо код має побічні ефекти, нам потрібно подбати. DI може витягнути побічні ефекти з функцій і ввести їх у параметри, зробивши ці функції більш випробуваними, але програма складнішими. Іноді це неминуче (наприклад, доступ до БД). Якщо код не має побічних ефектів, DI - це просто марна складність.
Варбо

13
@CarlLeth: DI - це одне рішення проблеми зробити код перевіряючим ізольовано, якщо він має залежності, які забороняють його. Але це не зменшує загальну складність, а також не робить код більш читабельним, а це означає, що він не обов'язково збільшує ремонтопридатність. Однак, якщо всі ці залежності можна усунути шляхом кращого розділення проблем, це ідеально «зводить нанівець» переваги ІД, оскільки це нівелює потребу в ДІ. Це часто є кращим рішенням для того, щоб зробити код більш випробуваним та одночасно ремонтом.
Док Браун

5
@Warbo Це було оригінальним і, мабуть, єдиним дійсним використанням глузування. Навіть на системних кордонах це потрібно рідко. Люди справді витрачають багато часу на створення та оновлення майже нікчемних тестів.
Френк Хілеман

6
@CarlLeth: гаразд, тепер я бачу, звідки беруться непорозуміння. Ви говорите про інверсію залежності . Але, питання, ця відповідь та мої коментарі стосуються DI = ін'єкції в депенсите .
Док Браун

36

На мій досвід, існує ряд недоліків ін'єкції залежності.

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

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

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


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

7
Якщо ви хочете протестувати клас із реальною реалізацією (не макет), ви можете написати функціональні тести - тести, подібні до тестових модулів, але не використовуючи макети.
BЈович

2
Я думаю, що ваш другий абзац повинен бути більш нюансованим: DI сам по собі не ускладнює (er) орієнтацію по коду. Найпростіше, DI - це просто наслідок слідування SOLID. Те, що збільшує складність, полягає у використанні непотрібних непрямих та рамки DI . Крім цього, ця відповідь вдаряє нігтем по голові.
Конрад Рудольф

4
Ін'єкційна залежність, за винятком випадків, коли це справді необхідне, також є попереджувальним знаком про те, що інший зайвий код може бути у великій кількості. Керівники часто дивуються, дізнавшись, що розробники додають складності заради складності.
Френк Хілеман

1
+1. Це справжня відповідь на запитання, яке було задано, і повинно бути прийнятим.
Мейсон Уілер

13

Я дотримувався поради Марка Семана з "Ін'єкції залежності в .NET" - щоб припустити.

DI слід застосовувати, коли у вас є "мінлива залежність", наприклад, є обґрунтований шанс, що він може змінитися.

Тож якщо ви думаєте, що у майбутньому у вас може бути більше однієї реалізації, або реалізація може змінитися, використовуйте DI. Інакше newце добре.


5
Зауважте, він також дає різні поради щодо OO та функціональних мов blog.ploeh.dk/2017/01/27/…
jk.

1
Це хороший момент. Якщо ми створимо інтерфейси для кожної залежності за замовчуванням, то це якось проти YAGNI.
Ландейо

4
Чи можете ви надати посилання на "Введення залежності в .NET" ?
Пітер Мортенсен

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

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

5

Про мого найбільшого домашнього улюбленця про ДІ вже згадувалось у кількох відповідях, але я трохи розгорнуся тут. DI (як це в основному робиться сьогодні, з контейнерами і т. Д.) Дійсно, ДУЖЕ шкодить читабельності коду. І читабельність коду, мабуть, є причиною більшості сучасних нововведень програмування. Як хтось сказав - писати код легко. Читання коду важко. Але це також надзвичайно важливо, якщо ви не пишете якусь крихітну утиліту одноразового виписування.

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

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

Щоб ефективно працювати з фрагментом коду, який використовував DI, ви повинні бути з ним знайомі і вже знати, що куди йде.


3

Увімкнення швидкого переключення реалізацій (наприклад, DbLogger замість ConsoleLogger)

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

Збільшена кількість занять

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

Створення непотрібних інтерфейсів

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

Чи слід використовувати ін'єкцію залежності, коли ми маємо лише одну реалізацію?

Так, але уникайте будь-яких доданих котельних плит .

Чи слід забороняти створювати нові об'єкти, окрім мовних / рамкових?

Ні. Буде багато класів цінностей (незмінних) та даних (змінних), де екземпляри просто створюються та передаються навколо, і де немає сенсу їх вводити, оскільки вони ніколи не зберігаються в іншому об'єкті (або лише в інших таких об’єктів).

Для них вам може знадобитися ввести фабрику замість цього, але більшу частину часу це не має сенсу (уявіть, наприклад, @Value class NamedUrl {private final String name; private final URL url;}вам тут дійсно не потрібна фабрика і нічого не потрібно вводити).

Чи є введення однієї реалізації поганою ідеєю (скажімо, у нас є лише одна реалізація, тому ми не хочемо створювати "порожній" інтерфейс), якщо ми не плануємо одиничне тестування певного класу?

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

Насправді в моєму поточному проекті є чотири класи (із сотень), які я вирішив виключити з DI, оскільки вони прості класи, які використовуються в занадто багатьох місцях, включаючи об’єкти даних.


Ще одним недоліком більшості фреймворків DI є накладні витрати. Це можна перенести на час компіляції (для Java, є Dagger , про інші мови немає).

Ще одним недоліком є ​​магія, що відбувається скрізь, яку можна налаштувати (наприклад, я відключив створення проксі під час використання Guice).


-4

Я мушу сказати, що, на мою думку, все поняття ін'єкції залежності є завищеним.

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

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

Тоді ви створюєте екземпляр класу Test і випробовуєте всі його методи. Деяким із вас це не буде зрозуміло - методи, які ви протестуєте, належать до тестуваного класу. І всі ці випробування методів відбуваються в одному файлі класу - одиничному класі тестування, пов'язаному з тестуваним класом. Тут нульові накладні витрати - ось як працює тестування одиниць.

У коді ця концепція виглядає приблизно так ...

class ClassUnderTest {

   protected final ADependency;
   protected final AnotherDependency;

   // Call from a factory or use an initializer 
   public void initializeDependencies() {
      aDependency = new ADependency();
      anotherDependency = new AnotherDependency();
   }
}

class TestClassUnderTest extends ClassUnderTest {

    @Override
    public void initializeDependencies() {
      aDependency = new MockitoObject();
      anotherDependency = new MockitoObject();
    }

    // Unit tests go here...
    // Unit tests call base class methods
}

Результат точно рівносильний використанню DI - тобто ClassUnderTest налаштований для тестування.

Єдині відмінності полягають у тому, що цей код абсолютно стислий, повністю інкапсульований, простіше кодувати, простіше зрозуміти, швидше, використовує менше пам'яті, не вимагає альтернативної конфігурації, не вимагає ніяких фреймворків, ніколи не стане причиною 4 сторінки (WTF!) Слід стека, який включає саме ZERO (0) класи, які ви написали, і цілком очевидний для всіх, хто має навіть найменші знання про ОО, від початківця до Гуру (ви могли б подумати, але помилитесь).

Це, звичайно, ми не можемо використати - це занадто очевидно і недостатньо модно.

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


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

1
його акуратно, коли у вас є лише один рівень спадкування класу та один рівень залежності. Невже це перетвориться на пекло на землі, коли воно розшириться?
Еван

4
якщо ви використовуєте інтерфейси і переміщуєте InitializeDependitions () в конструктор його такий же, але акуратніший. Наступним кроком додавання параметрів побудови означає, що ви зможете позбавити всіх своїх TestClasses.
Еван

5
У цьому стільки не так. Як говорили інші, ваш "еквівалент DI" зовсім не є введенням залежності, це антитеза, і демонструє повне нерозуміння концепції та вводить і інші потенційні підводні камені: частково ініціалізовані об'єкти є запахом коду, як Еван пропонує перенести ініціалізацію в конструктор і передати їх через параметри конструктора. Тоді у вас є ДІ ...
МістерМіндор

3
Додавання до @ Mr.Mindor: існує більш загальна анти-модель "послідовне з'єднання", яка не стосується лише ініціалізації. Якщо методи об’єкта (або, загалом, виклики API) повинні запускатися у певному порядку, наприклад, barможна викликати лише після foo, тоді це поганий API. Він вимагає надання функціональності ( bar), але насправді ми не можемо ним користуватися (оскільки, fooможливо, його не називали). Якщо ви хочете дотримуватися свого initializeDependencies(анти?) Шаблону, вам слід принаймні зробити його приватним / захищеним і автоматично викликати його від конструктора, щоб API був щирим.
Варбо

-6

Дійсно ваше запитання зводиться до "Чи погано тестування приладу?"

99% ваших альтернативних класів для ін'єкції будуть макетами, які дозволять перевірити одиницю.

Якщо ви зробите тестування одиниць без DI, у вас виникає проблема, як змусити класи використовувати знурені дані або глузливий сервіс. Скажімо, «частина логіки», оскільки ви, можливо, не розділяєте її на сервіси.

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

Важко уявити недолік настільки погано, що перевага одиничного тестування завищена.


13
Я не згоден з вашим твердженням, що DI стосується тестування одиниць. Полегшення тестування приладів - лише одна з переваг DI, і, мабуть, не найважливіша.
Роберт Харві

5
Я не погоджуюся з вашою передумовою, що тестування блоку та DI так близько. Використовуючи макет / заглушку, ми змушуємо тестовий набір лежати нам трохи більше: система, що перевіряється, отримує подальше від реальної системи. Це об'єктивно погано. Іноді це перевершує перевершення: знущаються дзвінки FS не потребують очищення; глузуючі HTTP-запити швидкі, детерміновані та працюють офлайн; На відміну від цього, щоразу, коли ми використовуємо жорстко закодований newвсередині методу, ми знаємо, що той самий код, який працює у виробництві, працював під час тестів.
Варбо

8
Ні, це не «чи погано тестування одиниць?», Це «глузування (а) справді необхідне, і (б) варто збільшення складності?» Це зовсім інше питання. Тестування блоків не погано (буквально ніхто цього не сперечається), і загалом це, безумовно, варто. Але не все тестування підрозділів вимагає глузування, і глузування несе значні витрати, тому його слід принаймні використовувати розумно.
Конрад Рудольф

6
@Ewan Після вашого останнього коментаря я не думаю, що ми згодні. Я кажу, що більшість одиничних тестів не потребують DI [фреймворків] , оскільки більшість одиничних тестів не потребують глузування. Насправді я навіть використовую це як евристику щодо якості коду: якщо більшість вашого коду не може бути перевірена одиницею без DI / макетних об'єктів, тоді ви написали неправильний код, який був занадто правильно пов'язаний. Більшість кодів має бути дуже відокремленим, одноосібним і загальним, а також тривіально перевіряти ізольовано.
Конрад Рудольф

5
@Ewan Ваше посилання дає чітке визначення одиничного тестування. За цим визначенням мій Orderприклад є одиничним тестом: це тестування методу ( totalметоду Order). Ви скаржитеся, що це дзвонить код з 2 класу, моя відповідь - це що ? Ми не тестуємо "2 класи відразу", ми тестуємо totalметод. Нас не повинно хвилювати, як метод виконує свою роботу: це деталі реалізації; тестування їх спричиняє неміцність, щільне з'єднання тощо. Ми дбаємо лише про поведінку методу (повернене значення та побічні ефекти), а не класи / модулі / регістри процесора / тощо. він використовується в процесі.
Варбо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.