Co je to Dependency Injection a jak funguje v Nette?
- David Grudl
- Nette Core | 8229
Kvůli zmatkům, které tematiku DI nerozlučně doprovází, raději trošku vysvětlení:
- Podstatou Dependency Injection (DI) je odebrat třídám zodpovědnost za získávání konkrétních objektů (tzv. služeb), které potřebují ke své činnosti a místo toho služby dostávají už při svém vytváření. Jsou jim předávány buď konstruktorem nebo přes settery. Právě o instancování a složení dohromady se stará DI kontejner.
- Nette\DI\Container představuje obecnou „továrničku“ na služby. Co umí se můžete dočíst tady
- V počátku běhu aplikace si musíme vytvořit prvotní DI kontejner (či více), který nám bude vytvářet všechny další služby (nebo kontejnery) a vytvoří vlastně celý objektový graf.
- Prvotní (systémový) kontejner Nette vytváří třída Nette\Config\Configurator
- Statickou obálkou nad tímto kontejnerem představuje třída Nette\Environment. Pokud si třída vyžádá službu přímo přístupem k Nette\Environment, nejde o Dependency Injection, ale o použití Service Locatoru. Ten je snadněji pochopitelný a pokud se vám zatím nedaří proniknout do DI, klidně ho můžete používat, svět se kvůli tomu nezboří :-)
- Souhrně se Dependency Injection + Service Locator označují termínem Inversion of Control (IoC)
- mkoubik
- Člen | 728
Já bych jenom dodal (pokud s tím souhlasíš), že jádro DI není ani tak v sestavování kontejnerů, jako spíš ve psaní vlastních tříd (nejčastěji modelů) tak, aby se jim daly podstrkávat závislosti přes konstruktor/settery – to se dá dělat i bez podpory ve frameworku.
To, že máte k dispozici DI kontejner (abyste ty závislosti nemuseli předávat po jedné) a kontejner builder (abyste se neupsali) je jenom bonus. Spousta lidí si myslí „to DI přece nemůže bejt tak jednoduchý, když je to takovej buzzword“. Podle mě dobří programátoři DI přístup používají běžně už delší dobu, nezávisle na tom, jak moc jim to framework usnadňuje.
- westrem
- Člen | 398
Neviem ci je toto spravny thread, ale pride mi, ze moja otazka sa tyka
zakladneho principu DI v Nette. Ak som pozorne studoval cely kod a
implementaciu DI v Nette, znamena kod na tomto
riadku ze ak mam v config.neon
nadeklarovane nejake
services
tak tieto pri svojom vytvarani nedostanu do konstruktoru
kontext (ergo Nette\DI\IContainer
)?
Mal som za to, ze toto ma byt jedna zo zakladnych funkcii DI v Nette. Je mozne, ze som vsak nieco prehliadol, budem rad ak by mi to niekto ozrejmil.
- Filip Procházka
- Moderator | 4668
Dependency Injection (=injektování/vkládání/předání závislostí) Je o tom, že každé třídě předáš to co potřebuje k existenci
class Sluzba
{
public function __construct(Sluzba2 $sluzba2, ISluzba3 $sluzba3)
{
$this->sluzba2 = $sluzba2;
$this->sluzba3 = $sluzba3;
}
public function dance()
{
if (!$this->iCanDance) {
$this->sluzba2->teachDance($this);
}
$this->sluzba3->danceWith($this);
}
}
Místo toho, aby je skrytě používali ve vybraných metodách
class Sluzba
{
public function dance()
{
if (!$this->iCanDance) {
$sluzba2 = Nette\Environment::getService('Sluzba2');
$sluzba2->teachDance($this);
}
$sluzba3 = Nette\Environment::getService('ISluzba3');
$sluzba3->danceWith($this);
}
}
- docílíš tím větší přehlednosti
- ztratíš magické schované závislosti, budeš jasně vědět, že když
když chceš používat
Sluzba
tak jí musíš dátSluzba2
aISluzba3
, jinak nebude fungovat - lépe se to testuje, můžeš podstrčit jiné instance, které budou kontrolovat co se kam předává (mockování)
- můžeš použít tu samou třídu na více věcí (jednou jí předáš
Sluzba10 implements ISluzba3
a podruhéSluzba50 implements ISluzba3
, bez toho abys musel nějak ovlivňovat „globální prostředí“ a zaneřádil container (serviceLocator) - … (další důvody směle vygooglit)
S tím že jí to můžeš předat různými způsoby (constructor, metoda, vlastnost, ..) A k tomu nám dopomáhej Dependency Injection Container, který se přímo stará o to, že třídám se předá to co potřebují. Na tobě je DIC nakonfigurovat tak, aby věděl co má čemu předat a ty pak nemuší psát ten kód, protože ho za tebe automatizovaně vykoná, podle tvé konfigurace. Navíc pak můžeš různě služby zase vyměňovat, …
A je to vůbec takové hezčí a voňavější OOP :)
- 22
- Člen | 1478
Mám dotaz k DI?
services
test-init:
factory: Test::init
test:
class: Test
arguments: ['@test-init']
class Test extends Object
{
public function __construct($container)
{
Debugger::dump($container);
}
public static function init(IContainer $container)
{
return $container;
}
}
zajímá mě, jestli je tohle jediný správný postup, jak dostat globální Container do constructoru nebo jestli toho lze docílit i jinak?
- Filip Procházka
- Moderator | 4668
Nejde to jinak.
Ale myslím si, že služby které ho potřebují, by měli být dostatečně důležité, abych je dal do továrniček do poděděného Configuratoru a všem ostatním předával jenom závislosti, které opravdu potřebují.
- 22
- Člen | 1478
dik, šlo mi jen o to, jestli jsem to celé pochopil správně, jsem si po
ránu s tím hrál, inspirovaný tvým příkladem na Gistu a Patrikovou
implementací
a má to jako něco do sebe.. :-)
Editoval 22 (4. 6. 2011 15:36)