Часті питання про DI (FAQ)

Чи є DI іншою назвою для IoC?

Inversion of Control (IoC) — це принцип, зосереджений на способі виконання коду: чи ваш код запускає чужий, чи ваш код інтегрований у чужий, який його потім викликає. IoC — це широкий термін, що охоплює події, так званий Голлівудський принцип та інші аспекти. Частиною цієї концепції є також фабрики, про які йдеться у Правило №3: залиште це фабриці, і які представляють інверсію для оператора new.

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

Отже, можна сказати, що DI є специфічною формою IoC. Однак не всі форми IoC є доцільними з точки зору чистоти коду. Наприклад, до антивзірців належать техніки, що працюють з глобальним станом або так званий Service Locator.

Що таке Service Locator?

Це альтернатива Dependency Injection. Він працює так, що створює центральне сховище, де реєструються всі доступні сервіси або залежності. Коли об'єкту потрібна залежність, він запитує її у Service Locator.

Однак, порівняно з Dependency Injection, він втрачає прозорість: залежності не передаються об'єктам безпосередньо і їх не так легко ідентифікувати, що вимагає дослідження коду для виявлення та розуміння всіх зв'язків. Тестування також складніше, оскільки ми не можемо просто передавати mock-об'єкти тестованим об'єктам, а повинні робити це через Service Locator. Крім того, Service Locator порушує дизайн коду, оскільки окремі об'єкти повинні знати про його існування, що відрізняється від Dependency Injection, де об'єкти не мають уявлення про DI-контейнер.

Коли краще не використовувати DI?

Немає відомих труднощів, пов'язаних з використанням патерну проектування Dependency Injection. Навпаки, отримання залежностей з глобально доступних місць призводить до цілій низці ускладнень, так само як і використання Service Locator. Тому доцільно використовувати DI завжди. Це не догматичний підхід, а просто не було знайдено кращої альтернативи.

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

Чи має використання DI свої тіньові сторони?

Чи несе використання Dependency Injection якісь недоліки, такі як підвищена складність написання коду або погіршена продуктивність? Що ми втрачаємо, коли починаємо писати код відповідно до DI?

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

При написанні коду буває необхідно створювати конструктори, що приймають залежності. Раніше це могло бути трудомістким, однак завдяки сучасним IDE та constructor property promotion це тепер питання кількох секунд. Фабрики можна легко генерувати за допомогою Nette DI та плагіна для PhpStorm кліком миші. З іншого боку, відпадає потреба писати синглтони та статичні точки доступу.

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

Як переписати legacy-додаток на DI?

Перехід від legacy-додатка до Dependency Injection може бути складним процесом, особливо для великих і комплексних додатків. Важливо підходити до цього процесу систематично.

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

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

Чому композиції надається перевага перед успадкуванням?

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

Чи можна використовувати Nette DI Container поза Nette?

Безумовно. Nette DI Container є частиною Nette, але він розроблений як самостійна бібліотека, яка може бути використана незалежно від інших частин фреймворку. Достатньо встановити її за допомогою Composer, створити конфігураційний файл з визначенням ваших сервісів, а потім за допомогою кількох рядків PHP-коду створити DI-контейнер. І одразу можете почати використовувати переваги Dependency Injection у своїх проектах.

Як виглядає конкретне використання, включаючи коди, описує розділ Nette DI Container.

Чому конфігурація у файлах 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 interface?

Nette DI Container не підтримує PSR-11 безпосередньо. Однак, якщо вам потрібна взаємодія між Nette DI Container та бібліотеками або фреймворками, які очікують PSR-11 Container Interface, ви можете створити простий адаптер, який слугуватиме мостом між Nette DI Container та PSR-11.

версія: 3.x