Na navigaci | Klávesové zkratky

DI a property injection

Dependency Injection je zřejmé předávání závislostí, tedy že se každá třída otevřeně hlásí ke svým závislostem, místo toho, aby je někde pokoutně získávala. Co kdyby se závislosti předávaly přímo do proměnných? Proberu úskalí a výhody property injection.

Property injection má jednu podstatnou výhodu: stručnost. Srovnejte:

class Foobar
{
	/** @var HttpRequest */
	private $httpRequest;

	/** @var Router */
	private $router;

	function __construct(HttpRequest $httpRequest, Router $router)
	{
		$this->httpRequest = $httpRequest;
		$this->router = $router;
	}

}

versus

class Foobar
{
	/** @var HttpRequest @inject */
	public $httpRequest;

	/** @var Router @inject */
	public $router;

}

Proměnné musíme definovat tak jako tak. Zpravidla u nich uvádíme i anotaci @var a příslušný datový typ. Je lákavé si ušetřit práci a místo psaní rutinního kódu konstruktoru nebo metody inject() doplnit prosté @inject. Property injection kromě minimálního režijního kódu navíc parádně řeší problém s předáváním závislostí a dědičností.

Použití anotace představuje jinou konvenci pro předání závislostí. Zdůrazňuji slovo jinou, protože ať už vyjmenujeme závislosti jakožto argumenty metody nebo anotováním, jde o ekvivalentní činnost. Čímž oponuji názoru, že použití anotace představuje závislost na kontejneru. To v žádném případě není pravda, jde jen o konvenci, koneckonců dosud o kontejnerech nepadla řeč a ukázky dávají smysl.

Stejně tak se nedívejte na anotaci @inject jako nějakou odpornou magii, kterou musíte nastudovat, abyste ji mohli používat. Žádná magie tu není. Jde o obyčejné veřejné proměnné a anotace je jen doplňující informace pro programátora, říkající, že objekt vyžaduje tyto proměnné naplnit. (Nutno dodat, že Jakub Vrána reagoval na použití anotací u private proměnných, což magie je.)

V článku o předávání závislostí jsem se používání proměnných širokým obloukem vyhnul, protože mají vážné nedostatky:

  • public proměnné nezajistí typovou kontrolu
  • public proměnné nezajistí neměnnost
  • private proměnné nelze naplnit žádnou jazykovou konstrukcí
  • private proměnné nejsou součástí veřejného API – nejde tedy o deklaraci závislosti!
  • pro protected proměnné platí nevýhody obou

Ještě bych přidal, že anotace nejsou nativní součástí jazyka PHP a jde tedy o nestandardní konvenci, oproti třeba injektáži přes konstruktor.

Poznámka: vstřikování závislostí do privátních proměnných posvětila třeba Java EE 6 a je to skutečně ee. Třída své závislosti tají (private = neveřejný) a nelze ji instancovat jinak, než kontejnerem (závislost na kontejneru). Jde zcela proti smyslu Dependency Injection, jak je popsán v perexu tohoto článku, a také proti základnímu principu OOP, zapouzdření. Označil bych to jako „Inversion of Dependency Injection.“

Pro properly property injection bychom potřebovali once-write-only veřejnou proměnnou s typovou kontrolou. Kdyby tohle PHP umělo, nic by nebránilo je používat. Jenže PHP to neumí.

Emulace inject property

PHP to neumí, ale lze to emulovat!

Emulaci zajistíme pomocí magických metod __set a __get. Jak ale dosáhnout toho, aby se k public proměnné přistupovalo skrze tyto metody? Použijeme trik: v konstruktoru ji unsetneme. Proměnná zmizí a při přístupu k ní se již použijí magické metody.

Příklad implementace ve formě základní třídy Object by mohl vypadat třeba takto:

class Object
{
	private $injects = array();

	function __construct()
	{
		// následující analýza proměnných by se mohla kešovat
		$rc = new ReflectionClass($this);
		foreach ($rc->getProperties() as $prop) {
			if ($prop->isPublic() && strpos($prop->getDocComment(), '@inject')
				&& preg_match('#@var\s+(\S+)#', $prop->getDocComment(), $m)
			) {
				// unset property to pass control to __set() and __get()
				unset($this->{$prop->getName()});
				$this->injects[$prop->getName()] = array('value' => null, 'type' => $m[1]);
			}
		}
	}


	function __set($name, $value)
	{
		if (!isset($this->injects[$name])) {
			throw new Exception("Cannot write to an undeclared property $$name.");

		} elseif ($this->injects[$name]['value']) {
			throw new Exception("Property $$name has already been set.");

		} elseif (!$value instanceof $this->injects[$name]['type']) {
			throw new Exception("Property $$name must be an instance of {$this->injects[$name]['type']}.");

		} else {
			$this->injects[$name]['value'] = $value;
		}
	}


	function __get($name)
	{
		if (!isset($this->injects[$name])) {
			throw new Exception("Cannot read an undeclared property $$name.");
		}
		return $this->injects[$name]['value'];
	}

}

pak stačí deklarovat výše uvedenou třídu Foobar jako potomka Object a vše bude fungovat standardně podle očekávání:

class Foobar extends Object
{
	/** @var HttpRequest @inject */
	public $httpRequest;

	/** @var Router @inject */
	public $router;

}

$fb = new Foobar;
$fb->router = new Router;

Navíc však máme zajištěnou neměnnost a typovou kontrolu:

$fb->router = new Router;
// Exception: Property $router has already been set.

$fb->httpRequest = new Router;
// Exception: Property $httpRequest must be an instance of HttpRequest.");

Čistá cesta nebo prasárna?

Zkusme se zamyslet nad tím, co vlastně anotace @inject představuje: hint pro programátora, že proměnnou má při vytváření objektu nastavit a že ji později nesmí měnit. Anotace @var pak nařizuje typ.

Je na programátorovi, aby dodržel kontrakt. Stejně jako v případě anotace @private v PHP 4 nebo JavaScriptu, či anotace @return v současném PHP. Jde o pravidla, u nichž se předpokládá, že je programátor dodrží, aniž to lze na úrovni interpreteru ověřit.

Třída Object rozšiřuje PHP o schopnost kontroly za běhu, usnadní tedy identifikaci chyb. Je to vychytávka navíc. Z mého pohledu tedy akceptovatelná cesta k použití property injection v PHP. Možná by se dalo uvažovat nad zařazením do Nette\Object a legitimizace této injektáže v Nette.


Komentáře

  1. maryo #1

    Je to trochu prasárna, ale IMO legitimní, přijde mi to nejvíc user friendly z těch možností co jsou. Už jsem tenhle „trik“ s unsetem někde použil, ale tam to moc legitimní nebylo :).

    před 12 lety | reagoval [2] maryo
  2. maryo #2

    #1 maryo, I když… Už se mi to zas tak nelíbí. Hlavně kvůli tomu, že je potřeba vytvořit reflexi pro každej novej objekt, to to radši předám do konstruktoru, zas tolik práce to neni ne? Jsem zvědavej, co na to zkušenější.

    před 12 lety | reagoval [3] David Grudl
  3. David Grudl #3

    avatar

    #2 maryo, Předpokládám, že by se analýza proměnných kešovala. Příklad jsem tím ale nechtěl komplikovat.

    před 12 lety | reagoval [4] maryo
  4. maryo #4

    #3 Davide Grudle, No ale pořád se budou jednou vytvářet a procházet reflection vlastnosti všech prvních instancí tříd dědících z Nette\Object i když pravděpodobně velká většina z nich vůbec @inject používat nebude :/. Přijde mi to jako velká daň i když by to možná chtělo udělat benchmark.

    před 12 lety | reagoval [6] David Grudl
  5. jasir #5

    avatar

    Moc pěkné, opravdu… Po implementaci cachování (musel jsem vyzkoušet) je overhead přibližně 10× větší než u prázdného konstruktoru, což je relativně ok.

    Nápad – o takhle k anotaci @inject přidat ještě @required? Stejně jako @inject slouží jako informace pro programátora, a ušetřili bychom psaní kódu pro kontrolu naplněnosti povinných závislostí?

    Ad. přidání do Nette\Object – jsem rozhodně pro, ale problém by mohlo být, že lidé nejsou zvyklí volat v třídách rozšiřujících Nette\Object konstruktor parentu … alespoň já to tak nedělám…

    před 12 lety | reagoval [6] David Grudl
  6. David Grudl #6

    avatar

    #4 maryo, zda vůbec a jak by to bylo implementované v Nette\Object neřeším, tohle je proof of concept bez optimalizací.

    #5 jasire, jaký by byl rozdíl mezi @inject a @required? Možná by bylo lepší použít @required namísto @inject.

    Jinak s tím konstruktorem v Nette\Object je komplikace i v tom, že by jej nebylo možné přepsat s nižší viditelností. Nejlepší by bylo najít nějaké řešení pro PHP 5.4 využívající traits.

    před 12 lety | reagoval [7] jasir
  7. jasir #7

    avatar

    #6 Davide Grudle, To je pravda, rozdíl by nebyl, nějak jsem nepřemýšlel.

    ad. Nette\Object – co varianta udělat chování volitelné přes nějaké volání $this->forcePropertyTypeChecking(). Plus mít možnost kvůli tuto vlastnost vypnout přes Nette\Object::$forcePropertyTypeChecking = false pro produkční mód ?

    před 12 lety
  8. Jan Dolecek #8

    avatar

    @required a @optional by mohlo slouzit jednak jako informace pro programatora, ze bez nejake zavislosti trida nemuze (resp. muze) fungovat. Znovupouzitelna komponenta ArticleCloud muze potrebovat ArticleRepository, ale Translator mit jen volitelny – pokud jej v aplikaci mam, tak jej pouziju pro lepsi poteseni uzivatele. Pokud nemam, tak to uzivatel nejak zvladne.
    Podobne treba optional zavislost na Cache/CacheStorage.

    Navic by tomu mohl rozumet DIContainer a usnadnit tim autowirovani. Pokud kontejner znal translator, tak by ho komponente rovnou predal, kdyz by ji vytvarel.

    před 12 lety
  9. Michal Gebauer #9

    avatar

    Takže, už zbývá vyřešit jen kontrolu, jestli je model/objekt připravený?

    Ohledně povinnosti značené pomocí slova required pak jako normální by měl být optional namísto inject, protože inject nevyjadřuje volitelnost. Nebo @inject required jako sousloví. Popřípadě @inject @required. Což by bylo asi nejčitelnější.

    před 12 lety
  10. Honza #10

    avatar

    Je to spíš prasárna než čisté řešení. Já bych se spokojil s tím, že konstruktoru předám objekt např. Properties $properies, který obsahuje reference na potřebné objekty typu Router a HttpRequest. Tento objekt $properties pak může mít metodu na kontrolu požadovaných objektů typu HttpRequest a Router a metoda vyhodí výjimku, pokud v $properties nebudou nastaveny.

    před 12 lety | reagoval [11] David Grudl
  11. David Grudl #11

    avatar

    Než @inject optional bych raději zvolil @var HttpRequest|null.

    #10 Honzo, chybí ti tam veřejná deklarace závislostí. Něco, z čeho by bylo patrné, jaké objekty se požadují.

    před 12 lety | reagoval [12] Honza
  12. Honza #12

    avatar

    #11 Davide Grudle, Bez té deklarace bych se klidně obešel, protože některé objekty můžou být povinné, jiné volitelné a některé i mezi sebou závislé, takže nějaká pevná deklarace nemusí mít ani smysl. Uvedl bych ty závislosti do komentáře ke konstruktoru, protože to mi při psaní kódu hodí hint, v případné dokumentaci to také bude a při spuštění se vždy provede kontrola (ta může být pro různé objekty různě složitá). Má to nějaké mínusy? Pokud ano, tak bych stejně raději použil první uvedenou možnost s vyjmenováním v konstruktoru.

    před 12 lety | reagoval [13] David Grudl
  13. David Grudl #13

    avatar

    #12 Honzo, potom jde o service locator, kterému se právě snažím vyhnout.

    před 12 lety
  14. Jakub Tesárek #14

    avatar

    Jsem z toho rozpačitý. Chápu, že tenhle článek nemá srovnávat property injection s jinými způsoby, ale stejně mi to nedá. Proč se nedržet constructor injection?

    • Slušností je, volat kontruktor předka jako úplně první věc v kontruktoru. Tím odpadá problém s neinicializovanými vlastnostmi z předka. A navíc je to logické. Nemůžu nastavovat „specifické“ vlastnosti, když ještě nemám nastavené „obecnější“ vlastnosti z parenta.
    • To psaní navíc mě taky moc neštve. Jsou místa, kde nemá smysl šetřit. A IDE mi ve spoustě věcí pomůže. Ale i kdyby nepomohlo, tak co?
    • Když budu na ty závislosti potřebovat private settery, tak je stejně budu muset napsat a ten kontruktor taky.
    před 12 lety | reagoval [16] David Grudl
  15. Aleš Roubíček #15

    avatar

    Constructor a Property injection jsou zcela validní techniky DI a mají dokonce svou semantiku, kterou @inject tak trochu porušuje.

    Injektovat by měla jít jakákoliv vlastnost, která má setter. Závislosti předávané pomocí setterů by měli být nepovinné – tj. takové, které objekt pro svou činnost nepotřebuje nutně. Naproti tomu konstruktorem by se měly injektovat závislosti povinné.

    Ty tvrdíš, že @inject nesouvisí s kontejnerem, ale pak je tu stejně ukázka s reflexí… Programátor pozná kde má injektovat díky setteru. Anotace je pro stroj a proto si za svým tvrzením stále stojím. :)

    před 12 lety | reagoval [16] David Grudl
  16. David Grudl #16

    avatar

    #14 Jakube Tesárku, proč nepoužít konstruktor injection? Tady a tady jsem se snažil dát obšírnou odpověď, proč to za určitých okolností v praxi nejde, lépe to napsat neumím a opakovat se nechci.

    #15 Aleši Roubíčku, s druhým odstavcem zcela souhlasím. A protože existují situace, kdy konstruktor, který preferuji, nelze z technických důvodů použít (viz odkazy), musí být nahrazen, třeba metodou inject() nebo anotací @inject.

    Je to ekvivalent uvedení závislosti jakožto parametru konstruktoru. Jiná forma téhož, syntaktický detail. Ukaž příklad, ve kterém to neplatí.

    (Použití reflexe se týkalo vnitřní implementace třídy, workaroundu jazykového omezení, s kontejnerem nesouvisí absolutně).

  17. Vena #17

    avatar

    Poznámka: vstřikování závislostí do privátních proměnných posvětila třeba Java EE 6 a je to skutečně ee. Třída své závislosti tají (private = neveřejný) a nelze ji instancovat jinak, než kontejnerem (závislost na kontejneru). Jde zcela proti smyslu Dependency Injection, jak je popsán v perexu tohoto článku, a také proti základnímu principu OOP, zapouzdření. Označil > bych to jako „Inversion of Dependency Injection.“

    Tak toto je snad největší blbost, kterou jsem tě kdy viděl napsat.

    1. Diskuze ohledně private field injektaže je v předchozím článku, o tom se tady bavit nebudu, mám na to jiný názor, každopadně – v Javě EE 6 tě nikdo nenutí injektovat závislosti přímo do private attributu. Můžeš si vybrat, jestli budeš injektovat přes konstruktor, setter nebo přímo do proměnné a navíc nezáleží na viditelnost (což beru jako plus), viz specifikace
    2. Java EE je jenom specifikace. Anotace @Inject je specifikací Javy EE 6 a je určená do prostředí, které implementuje Javu EE 6. O nějaké závislosti na kontejnéru nemůže být řeč.
    3. Třídu jde instanciovat jinak, než kontejnérem, tedy mimo prostředí Javy EE. Stačí vytvořit novou instanci pomocí operátoru new, stejně jako je to v PHP. Závislosti ji pak můžes taky nainjektovat, buď pomocí reflekse (pokud je oanotovaný private atribut nebo metoda), nebo klasicky.

    Očividně o Javě EE nic moc nevíš. Musíš si uvědomit, že Java EE neni framework, ale platforma, buď ji budeš používat (a budeš mít výhody jako je anotace @Inject), nebo nebudeš (a budeš používat „pouze“ Javu SE).

    Jinak máš pravdu, jde zcela proti smyslu DI, tak jak je popsán v perexu tvého článku. Naštěstí je anotace @Inject součástí specifkace CDI (což je jedna ze specifikací Javy EE 6), které tvou filozofii DI, kdy se třída otevřeně hlásí ke svým závislostem, nesdílí.

    Tuto poznámku jsis fakt mohl odpustit, děláš ze sebe ignoranta.

    před 12 lety | reagoval [18] David Grudl
  18. David Grudl #18

    avatar

    #17 Veno, prosím, už dost, „posvětila“ !== „nutí“, přečti si to znovu bez emocí, další komentáře o private property injection budu jako správný ignorant mazat ;)

    před 12 lety | reagoval [24] Vena
  19. Aleš Roubíček #19

    avatar

    #16 Davide Grudle, Problém s dědičností lze vždy vyřešit kompozicí. Proto si nemyslím, že „musí být nahrazen.“ Nemusí. :)

    před 12 lety | reagoval [20] David Grudl
  20. David Grudl #20

    avatar

    #19 Aleši Roubíčku, chápu to tedy jako souhlas ?

    před 12 lety
  21. Jakub Tesárek #21

    avatar

    #16 Davide Grudle, Tak jsem asi tenhle článek špatně pochopil. Tohle se týká výlučně presenterů? Ok, v nette se presentery dostaly dostaly do konstror hell. Stále si myslím, že nejlepším řešením by bylo zrefaktorovat presentery, nechat z presenteru jen inteface, pokud to jen trochu půjde tak se vyhnout dědění (nejen u presenterů ale obecně). Problem solved.

    Každopádně pokud se jedná o to, jak umožnit pěkné DI presenterů bez potřeby je zásadně refaktorovat, pak souuhlasím, constructor injection není optimální.

    před 12 lety | reagoval [22] David Grudl
  22. David Grudl #22

    avatar

    #21 Jakube Tesárku, v článku nic o presenterech nepadlo, proč by se jich měl týkat? Řeší čistě problematiku property injection, její výhody a nevýhody. Property injection jsem si nevymyslel, pouze jsem o tom napsal článek.

    před 12 lety | reagoval [23] Jakub Tesárek
  23. Jakub Tesárek #23

    avatar

    #22 Davide Grudle, Odkázal jsi mě na články, ve kterých měly argumenty proti mému tvrzení, že constructor injection je lepší. Nenašel jsem tam ale obecné argumenty, pouze vysvětlení, proč se nehodí tobě pro presentery v nette. Proto jsem se vrátil a vyptával se dál.

    A samozřejmě vím, že sis property injection nevymyslel, není mi deset ?

    Ale teď když ten článek čtu znovu, tak koukám, že jsem si jednu nebo dvě věty nějak domyslel z tvého článku o předávání závislostí presenterům a tak jsem sám sebe zmátl. Tímto se ti omlouvám

    před 12 lety | reagoval [25] David Grudl
  24. Vena #24

    avatar

    #18 Davide Grudle,
    Máš pravdu, myslel jsem, že narážíš na to, že @Inject se nedá použít mimo Javu EE. Omlouvám se za trošku arogantní příspěvek.

    Každopádně nechápu, proč se navážíš do Javy EE, když s ní nemáš žádou zkušenost.

    před 12 lety | reagoval [25] David Grudl
  25. David Grudl #25

    avatar

    #23 Jakube Tesárku, preferuji constructor injection, protože jde o nejsilnější konvenci mající oporu v jazyce. Obecné argumenty „proti“ jsem sepsal tady. (Píšu v uvozovkách, protože to nejsou argumenty proti, spíš jde o případy, kdy se stane constructor injection problematický). Tento článek v podstatě spíš před property injection v PHP varuje.

    #24 Veno, To není navážení, ale věcná kritika jedné featury. Můžeš jí klidně oponovat, ale ne ad hominem, tj. poukazováním na mé (ne)zkušenosti. Taková argumentace je pak demagogická.

    před 12 lety | reagoval [28] Vena
  26. lopata #26

    avatar

    #16 Davide Grudle,

    Je to ekvivalent uvedení závislosti jakožto parametru konstruktoru. > Jiná forma téhož, …

    To tedy není, když volám konstruktor je hned jasné, co potřebuje předat. Když to udělám špatně, PHP zařve ještě před voláním konstruktoru.

    Naproti tomu v téhle podivnosti není jasné z konstruktoru nic, nikdo nezařve, konstruktor se zavolá a k chybě dojde až při použití property.

    To jsou dost podstatné rozdíly a určitě se tedy nejedná o ekvivalentní chování.

    Nehledě na podstatné snížení výkonu a nafouknutí paměťových nároků při nějaké cachování. Tohle tedy určitě ne.

    před 12 lety | reagoval [27] David Grudl
  27. David Grudl #27

    avatar

    #26 lopato, psal jsem to v souvislosti se závislostí na kontejneru, v čemž si jsou ekvivalentní, protože žádnou závislost nemají. Jednotlivé injektáže pochopitelně ekvivalentní nejsou, jsou spíše diametrálně odlišené (cca 3 odstavec).

    Kdo dál?

    před 12 lety | reagoval [32] lopota
  28. Vena #28

    avatar

    #25 Davide Grudle,
    Ok, nechtěl jsem to už moc rozvádět, ale mám čas.

    Třída své závislosti tají (private = neveřejný) a nelze ji instancovat jinak, než kontejnerem (závislost na kontejneru).

    Nechápu, jak toto můžeš brát jako závislost na kontejnéru. Třída na něčem závisí, pokud je v ní nekde explicitně importovaná nebo používaná nějaká extérní komponenta.
    Ale ty to očividně bereš nějak jinak. Asi chceš říct, že té třídě musí injektovat nějaké závislosti kontejnér, protože normální kostrukcí to nelze.

    Jde zcela proti smyslu Dependency Injection, jak je popsán v perexu tohoto článku

    Toto odvozujěš na základě toho, že je potřeba kontejnér a třída je tedy na něčem extérním závislá, což by z principu DI být neměla.
    Jenže stejně tak bude ta třída závislá např. na standardní knihovně jazyka. To podle tebe neporušuje DI?
    Neporušuje, a já ti řeknu proč – DI není nějaký kontrakt, který třída musí dodržet, aby to byla DI-třída. DI je jenom návrhový vzor, který použiješ, když chceš některé závislosti vytáhnout ze třídy pryč. Nikdo ti neříká, aby jsi je vytáhnul pryč všechny, jinak nebude tvoje třída DI-třídou a nebude tedy tak cool. Nic jako porušení DI neexistuje.
    Obecně vytáhneš ze třídy ty závislosti, kterým by jsi mohl v budoucnu nebo při runtime chtít změnit implementaci. Toto je motivace DI, je to napsané i na wikipedii:

    The primary purpose of the dependency injection pattern is to allow selection among multiple implementations of a given dependency interface at runtime, or via configuration files, instead of at compile time.

    Takže závislost na kontejnéru nejde proti smyslu DI – pokud se rozhodneme používat nějaký kontejnér, nemyslím si, že ho budeme chtít vyměnit – stejně jako nebudeme chtít vyměnit třeba nette, je to sice závislost (presentery jsou závislé na nette), ale to nám přece vůbec nevadí, mi to nette používáme, používat chceme a používat budeme.

    a také proti základnímu principu OOP, zapouzdření.

    Jak jsem mnohokrát říkal, DI obecně porušuje zapouzdření.

    Já mám prostě pořád pocit, že DI někdo z nás nechápe.
    Z diskuze s tebou mi příjde, že DI chápeš jako nějaký kontrakt, konkrétně viz perex.
    Já chápu DI jako návrhový vzor, který je spíš definovaný svou motivací.

    před 12 lety | reagoval [29] David Grudl
  29. David Grudl #29

    avatar

    #28 Veno,

    Asi chceš říct, že té třídě musí injektovat nějaké závislosti kontejnér, protože normální kostrukcí to nelze.

    Přesně tak! A nevyjádřil jsem se přesně, zavislá je na injektoru. Je to moment, kdy jazyk popírá sám sebe, zkus se nad tím filosoficky zamyslet.

    ad druhý odstavec o smyslu Dependency Injection: jak to popisuješ, DI vůbec nevnímám. Vnímáme ho stejně, dle mého ho ale private property injection kompletně porušuje. Proč? Protože neumožňuje závislosti vůbec nastavit (normální konstrukcí, pochopitelně) a ani je deklarovat (privátní prvky se do veřejných API referencí vůbec neuvádí). Čímž se zase dostáváme k závislosti na injektoru.

    Jak jsem mnohokrát říkal, DI obecně porušuje zapouzdření.

    Já jsem zase mnohokrát říkal, že ne. Tady asi budeš potřebovat lepší argument ?

    před 12 lety | reagoval [30] Vena
  30. Vena #30

    avatar

    #29 Davide Grudle,

    Přesně tak! A nevyjádřil jsem se přesně, zavislá je na injektoru. Je to moment, kdy jazyk popírá sám sebe, zkus se nad tím filosoficky zamyslet.

    No, jak jsem říkal, kontejnér nelze brát jako závislost, která by mě obtěžovala, stejně tak standardní knihovna. Ale toto ti asi nevysvětlím, když DI tak jak ho popisuju nevnímaš :)

    Já jsem zase mnohokrát říkal, že ne. Tady asi budeš potřebovat lepší argument

    Myslel jsem, že to stačilo ve vedlejší diskuzi. Tak tedy znova. Závislosti jsou implementační detail. A imlemntační detaily by neměli být vidět skrz API. Další věc jě, že nějaká třetí strana musí vědět o závislostech mojích tříd. Diskuze na stackoverflow, kterou jsem odkazoval ve vedlejší diskuzi, to docela slušně objasňuje.

    před 12 lety | reagoval [31] bene
  31. bene #31

    #30 Veno, Tak jinak. Řekněme, že napíšeš nějakou knihovnu, závislou na onom injektoru.
    Pak tu knihovnu dáš k dispozici.
    Někdo ji bude chtít použít, ale používá jíný injektor nebo žádný, prostě chce jen vytvořit instanci.
    V tom případě musí člověk použít vždy tvůj/obecný injektor. V prvním případě má dva v druhém něco, co vůbec nechtěl používat.

    Nevím těď jak je to v java EE, pokud má nějakou přímou podporu pro @inject a SE ne. Ale pokud je to tak, a pokud napíšeš závislost na EE @inject, tak už to nepoužiješ v SE. To mi příjde jako obrovské omezení.

    Pokud ale použiješ standartní jazykové konstrukce pro injektování závislostí, tak nemusíš žadný kontejner/injektor použít a instanci si vytvoříš sám.
    DI a kontejnér neznamená totéž. DI je jen technika/doporučení, jak předat závislosti. Kontejnér je jen usnadnění, pro vytváření instancí objektů, které mohou, ale nemusí získávát své závyslosti technikou DI.

    Z toho jasně plyne, že private a předávání závislostí pomocí kontejneru je porušení DI. Protože objekt je „nepřímo“ závislý na onom injektoru.

    před 12 lety | reagoval [33] Vena
  32. lopota #32

    avatar

    #27 Davide Grudle, OK, takže ekvivalentní je property injection a konstruktor injection tedy jen v tom, že jde o DI.

    Pořád je tady to zhoršení výkonu a navýšení paměťové náročnosti u implementace. Když by se něco mělo někam zavádět, tak určitě bez těchto brzd. Celá implementace v nějakém frameworku by stačila tak, aby nějaký něco umělo vytvořit objekt a nastavit mu závislosti dle property injection bez toho, aniž by bylo nutné dědit od nějakého božského objektu. Mnohem raději se smířím s tím, že to nebude typově bezpečné atd., jsem v dynamickém programovacím jazyku a tam nic takového jako „once-write-only veřejnou proměnnou s typovou kontrolou“ nečekám, takže nechci, aby mi jí někdo nutil.

    před 12 lety | reagoval [34] David Grudl
  33. Vena #33

    avatar

    #31 bene,
    Pokud napíšeš knihovnu, která závisí na jiné knihovně, tak je logické, že klientský kód bude potřebovat obě knihovny, tak to funguje úplně všude.

    Java EE – Pokud napíšeš nějaký kód pro nette, těžko budeš chtít, aby to bylo kompatibilní třeba se Zendem. Java EE ja navíc mnohem víc, než jen framework.
    Pozn.: Ve skutečnosti můžeš anotaci @Inject používat i mimo prostředí Javy EE, stačí ti opatřit nějaký DI kontejnér, který běží standalone, např. hojně používaný Spring.

    Z toho jasně plyne, že private a předávání závislostí pomocí kontejneru je porušení DI. Protože objekt je „nepřímo“ závislý na onom injektoru.

    Nechápeš DI. Neslouží k tomu, aby jses zbavil závislostí (to je nemožné). Slouží k odstranění těsných vazeb a nahrazení jich volnými vazbami (viz loose coupling vs tight coupling). Ty závislosti, tak jak je prezentuješ ty a David tam budou vždycky, musí existovat nějaká třetí strana, která mi ty závislosti dodá.

    před 12 lety | reagoval [34] David Grudl [38] bene
  34. David Grudl #34

    avatar

    #32 lopoto, no tak to nepoužiješ, viz poslední odstavec článku. Jak prosté milý Watsone ?

    #33 Veno, nezabředávej stále do vysvětlování DI, v tom není spor. Problém je v tom, že u PPI musí existovat injektor, bez kterého jsi namydlený. Sám jsi psal, že DI je jenom návrhový vzor a neexistuje nic jako DI-třída. No a najednou tu máš třídy, které jsou DI, přesněji DIC-only, protože bez injektoru jsou nepoužitelné.

    DI jako návrhový vzor mohu používat nezávisle na jazyku, zatímco PPI je něco tvrdě svázaného s „frameworkem“.

    Taktéž privátní proměnné nejsou vidět v API dokumentaci, takže ani nevím, že tam nějaké závislosti jsou. (Namítneš, že to vědět nemusím, že to ví injektor? A jsme u toho, závislost na injektoru! Ale to pak není DI.) Už se opakuju, diskuse v kruhu nemá smysl, viď?

    před 12 lety | reagoval [35] Vena
  35. Vena #35

    avatar

    #34 Davide Grudle,
    Diskuze v kruhu je opravdu velmi únavná :)

    nezabředávej stále do vysvětlování DI, v tom není spor.

    Já mám právě pocit, že v tom je ten největší problém, podle mě tvoje definice DI z perexu je značne nepřesná.
    Až budeš mít někdy čas, zkus se podívat na tento článek. Je to od Martina Fowlera.
    Tady bych vypíchnul jen to, že takto vypadá klasický schéma problému, které se snaží DI řešit:

    [* https://martinfowler.com/…es/naive.gif *]

    Problém je v tom, že třída MovieLister je přímo závislá na implementaci rozhraní MovieFinder.
    Řešení:

    [* https://martinfowler.com/…injector.gif *]

    Zbavili jsme se přímé závislosti na implementaci rozhraní MovieFinder! Hurá. Ale cosik nám tam přibylo. Říká se tomu různě, např. injektor. Toto schéma je nezávislé na tom, jakou techniku DI zvolíme! Ať je to DI přes konstruktor nebo přes PPI.

    Toto je DI, tak jak tomu rozumím já, Fowler (jedna z největších Java EE mastermind) a snad celá Java komunita.

    Pokud mi nevěříš a nevěříš ani Fowlerovi, tak si myslím, že tuto diskuzi můžeme ukončit a dále se netočit dokola :)

    před 12 lety | reagoval [36] David Grudl
  36. David Grudl #36

    avatar

    #35 Veno, Fowler má pravdu. A ano, diskusi můžeme ukončit.

    před 12 lety
  37. jirka d. #37

    avatar

    Vzhledem k tomu, že jsem se podobné property injection pokoušel nad Nette implementovat před cca dvěma měsíci, ale bez toho čupr triku s unsetem v konstruktoru, takže to bylo implementačně trochu k ničemu :) dávám za sebe plus bod pro property injection. Má to i výhodu v tom, že se relativně jednodušše dají všechny závislosti vizuálně zobrazit, dále to při čtení kodu vypada dost dobře :) Drobny problem odstranitelny zcasti kesovanim, je akorat ta vykonnost, co se prasarny tyce: i nektery low-level veci v kernelech modernich operacnich systemu ukryvaji o dost vetsi praseciny a nikdo za to programatory nevesi na zapaleny krize cestou do programatorskeho Rima…proste se to podle me jednodusejc efektivne napsat neda…mozna cesta do budoucna je implementace DI pres traits (sice nemam zadnou predstavu, jak by to melo aspon mlhave vypadat, ale co…:) ) jenze kdy bude bezne na hostingu dostupna 5.4 v odladeny verzi (typicky 5.x.3 a vyssi)? To muze trvat taky par let…

    před 12 lety
  38. bene #38

    #33 Veno, A to je právě ono. Pokud napíši v Nette knihovnu která používá contructor/setter/public property pro nastaven9 zavislostí, můžu ji pak používat v Zendu a přes Zendovský konterjner si budu instance této knihovny vytvářet. Stejně tak naopak. Kdežto při nutné závislosti na konkrétním injektoru budu muset kopírovat i ten injektor.
    Stejně tak v JAVE, když napíši knihovnu pomocí POJO tříd, tak ji mohu použít kdekoliv a zvolit si libovolný kontejner.

    Ohledně závislostí, asi jsem se špatně vyjádřil. Samozřejmě že se jich nezbavíš, ale každá třída se k nim veřejně hlásí (to beru jako princip DI). Pokud bereš jako součást každé knihovny i konkrétní injektor, ok

    Pokud někdo brere DI jako návrhový vzor, ok. Pro mě je to jen princip že se třída k závislostem veřejně hlásí a nezískává je nějakým jiným způsobem. Jako návrhový vzor bych pak mohl označit i to: Nepoužívejte public property, ale settery, protože tak můžete v budoucnu např. validovat délku řetězce. Pokud samozřejmě jazyk neumožňuje property jako např. C#.

    Každý to vidíme trochu jinak, zůstaneme u toho ?

    před 12 lety
  39. Ondra #39

    Óóó, krása… ještě dva tři roky a fakt přejdeš na Javu, kde tyhle jsou tyhle věci leta :)

    před 12 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.


phpFashion © 2004, 2024 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.