Често задавани въпроси за DI (FAQ)
- DI ли е друго име за IoC?
- Какво е Service Locator?
- Кога е по-добре да не се използва DI?
- Има ли използването на DI своите недостатъци?
- Как да пренапишем legacy приложение към DI?
- Защо се предпочита композиция пред наследяването?
- Може ли да се използва Nette DI Container извън Nette?
- Защо е конфигурацията в NEON файлове?
- Не забавя ли приложението парсването на NEON файлове?
- Как да получа достъп до параметрите в конфигурационния файл от моя клас?
- Поддържа ли Nette PSR-11: Container interface?
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. Това не е догматичен подход, а просто не е намерена по-добра алтернатива.
Въпреки това съществуват определени ситуации, в които не предаваме обекти, а ги получаваме от глобалното пространство. Например, при дебъгване на код, когато трябва да изведете стойността на променлива в определена точка от програмата, да измерите продължителността на определена част от програмата или да запишете съобщение. В такива случаи, когато става въпрос за временни действия, които по-късно ще бъдат премахнати от кода, е легитимно да се използва глобално достъпен dumper, хронометър или logger. Тези инструменти всъщност не принадлежат към дизайна на кода.
Има ли използването на DI своите недостатъци?
Носи ли използването на Dependency Injection някакви недостатъци, като например повишена сложност при писане на код или влошена производителност? Какво губим, когато започнем да пишем код в съответствие с DI?
DI не влияе на производителността или изискванията за памет на приложението. Производителността на DI Container-а може да играе известна роля, но в случая на 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 е инвестиция в качеството на кода и дългосрочната поддръжка на приложението. Въпреки че може да е предизвикателство да се направят тези промени, резултатът трябва да бъде по-чист, по-модулен и лесно тестваем код, който е готов за бъдещи разширения и поддръжка.
Защо се предпочита композиция пред наследяването?
По-подходящо е да се използва композиция вместо наследяване, тъй като тя служи за повторно използване на код, без да се налага да се притесняваме за последствията от промените. Следователно тя осигурява по-слаба връзка, при която не трябва да се притесняваме, че промяната на някой код ще доведе до необходимост от промяна на друг зависим код. Типичен пример е ситуацията, наречена constructor hell.
Може ли да се използва Nette DI Container извън Nette?
Определено. Nette DI Container е част от Nette, но е проектиран като самостоятелна библиотека, която може да се използва независимо от другите части на framework-а. Просто го инсталирайте с помощта на Composer, създайте конфигурационен файл с дефиницията на вашите сървиси и след това използвайте няколко реда PHP код, за да създадете DI контейнера. И веднага можете да започнете да се възползвате от предимствата на Dependency Injection във вашите проекти.
Как изглежда конкретното използване, включително кодове, е описано в главата Nette DI Container.
Защо е конфигурацията в NEON файлове?
NEON е прост и лесен за четене конфигурационен език, разработен в рамките на Nette за настройка на приложения, сървиси и техните зависимости. В сравнение с JSON или YAML, той предлага много по-интуитивни и гъвкави опции за тази цел. В NEON могат естествено да се опишат връзки, които в Symfony & YAMLu би било невъзможно да се запишат изобщо или само чрез сложно описание.
Не забавя ли приложението парсването на NEON файлове?
Въпреки че NEON файловете се парсват много бързо, на този аспект изобщо няма значение. Причината е, че парсването на файловете се извършва само веднъж при първото стартиране на приложението. След това се генерира кодът на DI контейнера, записва се на диска и се изпълнява при всяка следваща заявка, без да е необходимо допълнително парсване.
Така работи в продукционна среда. По време на разработка NEON файловете се парсват всеки път, когато съдържанието им се промени, така че разработчикът винаги да има актуален DI контейнер. Самото парсване е, както беше споменато, въпрос на момент.
Как да получа достъп до параметрите в конфигурационния файл от моя клас?
Нека си припомним Правило № 1: нека ти го предадат. Ако класът изисква информация от конфигурационния файл, не е нужно да мислим как да стигнем до тази информация, вместо това просто я искаме – например чрез конструктора на класа. И осъществяваме предаването в конфигурационния файл.
В този пример %myParameter%
е placeholder за стойността на параметъра
myParameter
, която се предава на конструктора на класа MyClass
:
# config.neon
parameters:
myParameter: Some value
services:
- MyClass(%myParameter%)
Ако искате да предавате повече параметри или да използвате autowiring, е препоръчително да опаковате параметрите в обект.
Поддържа ли Nette PSR-11: Container interface?
Nette DI Container не поддържа директно PSR-11. Въпреки това, ако се нуждаете от оперативна съвместимост между Nette DI Container-а и библиотеки или framework-ове, които очакват PSR-11 Container Interface, можете да създадете прост адаптер, който ще служи като мост между Nette DI Container-а и PSR-11.