Co je to Dependency Injection a jak funguje v Nette?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 8229
+
+1
-

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
+
0
-

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
+
0
-

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
+
+1
-

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át Sluzba2 a ISluzba3, 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 :)

Michalek
Člen | 211
+
0
-

Hurá! Proč se tohle takhle nenapsalo hned na začátku? Hurá! Pokud se někdo bude ptát o co jde, tenhle příspěvek je jasná volba.

22
Člen | 1478
+
0
-

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
+
0
-

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
+
0
-

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)