Поширені запитання про DI (FAQ)
Чи є DI іншою назвою для IoC?
Inversion of Control (IoC) – це принцип, який фокусується на тому, як
виконується код – чи ваш код ініціює зовнішній код, чи ваш код
інтегрується в зовнішній код, який потім викликає його. IoC – це широке
поняття, яке включає в себе події, так
званий голлівудський
принцип та інші аспекти. Фабрики, які є частиною Правила №3: Нехай фабрика
розбирається з цим і представляють собою інверсію для оператора
new
, також є компонентами цієї концепції.
Dependency Injection (DI) – це те, як один об'єкт знає про інший об'єкт, тобто залежність. Це патерн проектування, який вимагає явної передачі залежностей між об'єктами.
Таким чином, можна сказати, що DI є специфічною формою IoC. Однак не всі форми IoC підходять з точки зору чистоти коду. Наприклад, до антипаттернів ми відносимо всі техніки, які працюють з глобальним станом або так званим Service Locator.
Що таке Service Locator?
Сервіс-локатор – це альтернатива ін'єкції залежностей. Він працює шляхом створення центрального сховища, де реєструються всі доступні сервіси або залежності. Коли об'єкту потрібна залежність, він запитує її з Service Locator.
Однак, порівняно з Dependency Injection, він втрачає прозорість: залежності не передаються безпосередньо об'єктам, і тому їх не так легко ідентифікувати, що вимагає вивчення коду, щоб виявити і зрозуміти всі зв'язки. Тестування також ускладнюється, оскільки ми не можемо просто передати імітаційні об'єкти об'єктам, що тестуються, а повинні пройти через Service Locator. Крім того, Service Locator порушує дизайн коду, оскільки окремі об'єкти повинні знати про його існування, що відрізняється від Dependency Injection, де об'єкти не знають про контейнер DI.
Коли краще не використовувати DI?
Не існує відомих труднощів, пов'язаних з використанням патерну проектування Ін'єкція залежностей (Dependency Injection). Навпаки, отримання залежностей з глобально доступних місць призводить до ряду ускладнень, так само як і використання Service Locator. Тому бажано завжди використовувати DI. Це не догматичний підхід, просто кращої альтернативи не знайдено.
Однак є певні ситуації, коли ми не передаємо об'єкти один одному, а отримуємо їх з глобального простору. Наприклад, при налагодженні коду і необхідності вивести значення змінної в певній точці програми, виміряти тривалість виконання певної частини програми або записати повідомлення в лог. У таких випадках, коли мова йде про тимчасові дії, які згодом будуть видалені з коду, цілком виправданим є використання глобально доступного дампера, секундоміра або логгера. Ці інструменти, зрештою, не належать до проектування коду.
Чи має використання DI свої недоліки?
Чи має використання Dependency Injection якісь недоліки, такі як збільшення складності написання коду чи погіршення продуктивності? Що ми втрачаємо, коли починаємо писати код відповідно до DI?
DI не впливає на продуктивність програми або вимоги до пам'яті. Продуктивність DI-контейнера може відігравати певну роль, але у випадку Nette DI контейнер компілюється в чистий PHP, тому його накладні витрати під час виконання програми практично дорівнюють нулю.
Під час написання коду необхідно створювати конструктори, які приймають залежності. Раніше це могло зайняти багато часу, але завдяки сучасним IDE і розширенню властивостей конструкторів, тепер це справа кількох секунд. За допомогою Nette DI і плагіна PhpStorm можна легко створювати фабрики всього за кілька кліків. З іншого боку, немає необхідності писати синглетони і статичні точки доступу.
Можна зробити висновок, що правильно спроектований додаток з використанням DI не є ні коротшим, ні довшим у порівнянні з додатком, що використовує синглетони. Частини коду, що працюють з залежностями, просто витягуються з окремих класів і переміщуються в нові місця, тобто в контейнер DI і фабрики.
Як переписати старий додаток на DI?
Міграція зі старого додатку на Dependency Injection може бути складним процесом, особливо для великих і складних додатків. Важливо підходити до цього процесу систематично.
- При переході на Dependency Injection важливо, щоб всі члени команди розуміли принципи і практики, які використовуються.
- По-перше, проведіть аналіз існуючого додатку, щоб визначити ключові компоненти та їх залежності. Складіть план, які частини будуть рефакторингуватися і в якому порядку.
- Впровадьте DI-контейнер або, ще краще, використовуйте існуючу бібліотеку, таку як Nette DI.
- Поступово рефакторингуйте кожну частину програми для використання Dependency Injection. Це може включати модифікацію конструкторів або методів, щоб вони приймали залежності в якості параметрів.
- Змініть місця в коді, де створюються об'єкти залежностей, так, щоб замість цього залежності інжектувалися контейнером. Це може включати використання фабрик.
Пам'ятайте, що перехід на інжекцію залежностей – це інвестиція в якість коду і довгострокову стійкість програми. Хоча внести ці зміни може бути складно, в результаті ви отримаєте чистіший, більш модульний і легко тестуємий код, готовий до майбутніх розширень і обслуговування.
Чому композиція краща за успадкування?
Краще використовувати композицію замість успадкування, оскільки вона дозволяє повторно використовувати код, не турбуючись про наслідки змін. Таким чином, це забезпечує більш слабкий зв'язок, коли нам не потрібно турбуватися про те, що зміна одного коду призведе до необхідності зміни іншого залежного коду. Типовим прикладом є ситуація, яку називають пеклом конструктора.
Чи можна використовувати Nette DI Container за межами Nette?
Безумовно, так. Nette DI Container є частиною Nette, але розроблений як окрема бібліотека, яку можна використовувати незалежно від інших частин фреймворку. Просто встановіть її за допомогою Composer, створіть конфігураційний файл, що визначає ваші сервіси, а потім за допомогою декількох рядків PHP-коду створіть DI-контейнер. І ви можете одразу ж почати використовувати переваги Dependency Injection у своїх проектах.
У розділі Контейнер Nette DI описано, як виглядає конкретний варіант використання, включно з кодом.
Чому конфігурація зберігається у файлах NEON?
NEON – це проста і зрозуміла мова конфігурації, розроблена в рамках Nette для налаштування програм, сервісів та їх залежностей. У порівнянні з JSON або YAML, вона пропонує набагато більш інтуїтивно зрозумілі та гнучкі можливості для цього. У NEON ви можете природно описувати прив'язки, які неможливо було б написати в Symfony & YAML або взагалі, або тільки через складний опис.
Чи сповільнює розбір NEON-файлів роботу програми?
Хоча NEON-файли розбираються дуже швидко, цей аспект не має особливого значення. Причина в тому, що розбір файлів відбувається лише один раз під час першого запуску програми. Після цього генерується код DI-контейнера, який зберігається на диску і виконується при кожному наступному запиті без необхідності подальшого розбору.
Саме так це працює у виробничому середовищі. Під час розробки NEON-файли аналізуються щоразу, коли змінюється їхній вміст, що гарантує, що розробник завжди має актуальний DI-контейнер. Як згадувалося раніше, фактичний синтаксичний аналіз – це справа однієї миті.
Як отримати доступ до параметрів з конфігураційного файлу в моєму класі?
Пам'ятайте про Правило №1: Дозвольте, щоб вам передали. Якщо класу потрібна інформація з конфігураційного файлу, нам не потрібно з'ясовувати, як отримати доступ до цієї інформації; натомість, ми просто запитуємо її – наприклад, через конструктор класу. І виконуємо передачу у конфігураційному файлі.
У цьому прикладі %myParameter%
– це заповнювач для значення
параметра myParameter
, яке буде передано в конструктор MyClass
:
# config.neon
parameters:
myParameter: Some value
services:
- MyClass(%myParameter%)
Якщо ви хочете передати декілька параметрів або використовувати автопідстановку, корисно обернути параметри в об'єкт.
Чи підтримує Nette інтерфейс PSR-11 Container?
Nette DI Container не підтримує PSR-11 безпосередньо. Однак, якщо вам потрібна сумісність між Nette DI Container і бібліотеками або фреймворками, які очікують інтерфейс PSR-11 Container Interface, ви можете створити простий адаптер, який слугуватиме мостом між Nette DI Container і PSR-11.