Често задавани въпроси за DI (FAQ)
DI ли е другото име на IoC?
Инверсия на контрола (IoC) е принцип, насочен към начина, по който
се изпълнява кодът – дали вашият код инициира външен код или вашият
код е интегриран във външен код, който след това го извиква. IoC е широка
концепция, която включва събития,
така наречения холивудски
принцип и други аспекти. Фабриките, които са част от Правило № 3: Нека фабриката да се
справи с него, и представляват инверсия за оператора new
, също
са компоненти на тази концепция.
Dependency Injection (DI) е за това как един обект знае за друг обект, т.е. за зависимостта. Това е шаблон за проектиране, който изисква изрично предаване на зависимости между обектите.
Следователно може да се каже, че DI е специфична форма на IoC. Не всички форми на IoC обаче са подходящи от гледна точка на чистотата на кода. Например сред антимоделите включваме всички техники, които работят с глобално състояние или така наречения Service Locator.
Какво представлява Service Locator?
Service Locator е алтернатива на Dependency Injection. Той работи чрез създаване на централно хранилище, в което се регистрират всички налични услуги или зависимости. Когато даден обект се нуждае от зависимост, той я заявява от 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.
- Постепенно преработете всяка част от приложението, за да използвате впръскване на зависимости. Това може да включва модифициране на конструктори или методи, за да приемат зависимости като параметри.
- Променете местата в кода, където се създават обекти на зависимости, така че вместо това зависимостите да се инжектират от контейнера. Това може да включва използването на фабрики.
Не забравяйте, че преминаването към инжектиране на зависимости е инвестиция в качеството на кода и дългосрочната устойчивост на приложението. Въпреки че може да е предизвикателство да се направят тези промени, резултатът трябва да бъде по-чист, по-модулен и лесно тестваем код, който е готов за бъдещи разширения и поддръжка.
Защо композицията е за предпочитане пред наследяването?
За предпочитане е да се използва композиция вместо наследяване, тъй като тя служи за повторно използване на кода, без да се налага да се притеснявате за последствията от промените. По този начин се осигурява по-хлабава връзка, при която не е необходимо да се притесняваме, че промяната на някакъв код ще доведе до необходимостта от промяна на друг зависим код. Типичен пример за това е ситуацията, наречена " ад на конструкторите".
Може ли 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?
Nette DI Container не поддържа PSR-11 директно. Въпреки това, ако се нуждаете от оперативна съвместимост между Nette DI Container и библиотеки или рамки, които очакват PSR-11 Container Interface, можете да създадете прост адаптер, който да служи като мост между Nette DI Container и PSR-11.