Když se mluví o Dependency Injection, bývá zmiňován i service locator, jako jakési zlé dvojče. O co se vlastně jedná?
Dependency Injection jsem v prvním článku popsal jako zřejmé předávání závislostí, tedy že se každá třída hlásí ke svým závislostem v konstruktoru nebo jiné metodě, místo toho, aby je někde v těle pokoutně získávala z globálního přístupového bodu. Dodržování tohoto principu vede ke srozumitelnějšímu a předvídatelnějšímu kódu.
Nicméně na vás číhá nástraha v podobě service locatoru.
Service locator je velechytrý objekt, který umí vrátit veškeré závislosti, které třída potřebuje, nebo i nepotřebuje. Pokud by si všechny třídy předávaly jeden service locator, předaly by si tak v jediném parametru všechny závislosti.
class Authenticator
{
private $locator;
function __construct(ServiceLocator $locator)
{
$this->locator = $locator;
}
function authenticate($user, $pass)
{
$database = $this->locator->getDatabase();
$database->query(...)
}
}
Bohužel Service Locator není v souladu s DI.
Proč? Není v souladu s tím, že předávání závislostí je zřejmé a
že se třída ke svým závislostem otevřeně hlásí.
Třída Authenticator
- potřebuje databázi, ale hlásí se k velmi obecnému service locatoru, což je v naprostém nepoměru vůči tomu, co skutečně potřebuje
- že potřebuje zrovna databázi se nedá zjistit jinak, než zkoumáním její implementace
Třída se tedy musí hlásit ke všem svým závislostem a právě jen k nim. Jinak o svých závislostech lže.
(Může nastat situace, kdy požadovat service locator je korektní: pokud ho třída skutečně jako takový potřebuje. Třeba kvůli jeho vizualizaci apod.)
Co naopak service locator není
Občas někdo pojmenuje validní konstrukci termínem service locator a na základě toho ji odsoudí. Podobný styl uvažování je zavádějící. Znamená, že něco používáme či zavrhujeme a už nevím proč vlastně.
Důležité je si uvědomit, co je tím špatným na service locatoru, tj. proč jej řadíme mezi antipatterny, a ověřit, jestli naše konstrukce netrpí stejnými vadami. Tedy diskuse o tom, zda jde o service locator nebo ne, je pak podružná.
Jako příklad si ukažme refaktoringu třídy Foo se třemi závislostmi:
class Foo
{
function __construct(A $a, B $b, C $c)
}
$foo = new Foo($a, $b, $c);
Závislosti vytkneme do jedné (immutable) třídy:
class FooDependencies
{
function __construct(A $a, B $b, C $c)
}
class Foo
{
function __construct(FooDependencies $d)
}
$foo = new Foo(new FooDependencies($a, $b, $c));
Z hlediska DI jsou obě alternativy korektní, třídy se hlásí ke všem svým závislostem a právě jen k nim. Neplatí tu námitky proti service locatoru. Samozřejmě je otázka, zda uvedený refactoring byl opodstatněný a správný, ale to už je jiný příběh.
Obdobně v úvodním článku Co je
Dependency Injection, kde jsem skupinu závislostí třídy
Article
v presenteru zredukoval na továrničkou
ArticleFactory
, nejsou na místě obavy, že továrnička je
service locator. Nevykazuje totiž jeho negativní rysy. Stejně tak
i příklad v článku Dependency
Injection versus Lazy loading, kde Authenticator
byl nahrazen
za AuthenticatorAccessor
, neboť presenter chtěl službu získat
skrze lazy loading.
Jak vidno, v DI nejde jen o jakékoliv předávání závislostí. Musí být zřejmé. A pokud si nejste jisti, zda používáte korektní objektový návrh v souladu s DI, udělejte si test „slepým API“. Skryjte si těla metod nebo vygenerujte API dokumentaci a závislosti jednotlivých tříd musí být stále jednoznačně patrné.
Všechny části:
- Co je Dependency Injection?
- "Dependency Injection versus Service Locator (právě čtete)
- Dependency Injection a předávání závislostí
- Dependency Injection a property injection
- Dependency Injection versus Lazy loading
Komentáře
Aleš Roubíček #1
Problém Acessoru ovšem neni ve skrývání závislostí, ale v nepredikovatelné životnosti jím poskytovaného objektu. Factory by se třeba měla starat nejen o vytvoření nového objektu, ale i o jeho ekologické zlikvidování. To Accessor zjevně nedělá.
V PHP je to pohodička, tam žádný objekt nemá životnost delší než jeden request a sdílený stav mezi thready taky řešit nemusíte, ale obecně je tohle nebezpečná technika (memory leaks, race conditions apod.).
Daniel Milde #2
Už to zaznělo na Twitteru, takže jen zopakuji: Použitím FooDependencies porušujeme Demeterův zákon.
Mirek #3
#2 Danieli Milde, Jen při předpokladu, že na FooDependencies někdo automaticky udělá getA()->cokoliv(). Pravděpodobně to ovšem udělá jen ten, kdo to automaticky předpokládá. Ostatní se nebudou obtěžovat už ani s existencí getA(), getB().. atd.
Jiří Landsman #4
#3 Mirku, V momentě kdy FooDependencies bude tvořit fasádu nad objekty v jejím konstruktoru tak to bude ok. Pokud to bude jen nějaká třída s veřejnýmy vlastnostmi $a, $b, $c tak to ok nebude. Je trochu škoda že je vidět pouze konstruktor
Mirek #5
#4 Jiří Landsmane, Samotná existence FooDependencies Demetru neporušuje. Ani nemůže, protože Demetra existenci tříd nijak nereguluje. Ostatně porušit Demetru, lze s úplně libovolnou třídou, pokud má neprázdné rozhraní. Jde totiž jen o to, co se s tím rozhraním kde dělá. Čili tu máme pěknou tautologii: s využitím FooDependencies nedojde k porušení Law of Demeter pokud toto využití neporušuje Law of Demeter. Proto mi přišlo, že reakce Daniela Milda ve skutečnosti znamená: „Kdybych psal FooDependecies já, to byste se červenali, jak bych s ní ohnul Demetru!“
Valerián Kadrnos #6
A neexistuje snad tisíc palčivějších problémů? Za humny nemají děti co jíst a vy řešíte FooDependencies. Že Vám není hanba!
David Grudl #7
#2 Danieli Milde, zákon (té) Démétér. Ale podobná zvolání bez jakéhokoliv vysvětlení jsou důvodem, proč jsem už dvakrát komentáře utnul. Většina čtenářů nebude tušit, o co se vlastně jedná a znejistí je to. Je to podobné, jako kdybych prohlásil, že to porušuje Law of Singular Form, což zní hrozivě, ale teprve s vysvětlením nad tím čtenář mávne rukou.
#4 Jiří Landsmane,
Předpokládejme proto tu korektní verzi, tj. že jde o immutable objekt. Příkladem jsem chtěl demonstrovat rozdíl oproti service locatoru a trošku si nahrát na další díl.
Jiří Landsman #8
#7 Davide Grudle, Jasný chápu :) V kontextu članku je opravdu implementace te tridy nepodstatna :) V kontextu těch komentářů už tolik ne ale ty už zase odbihaj od tamatu článku.
Mirek #9
#7 Davide Grudle, prosím, k čemu je dobrý předpoklad immutability?
mishak #10
#9 Mirku, V tom, že objekt slouží jako kontejner, a nemůžeš měnit po vytvoření parametry se kterými pracuje, nebo cokoli co z nich objekt vevnitř poskládá. Prakticky FooDependencies vydestiluje z těchto tří parametrů co opravdu potřebuješ. Předáš např.: látku, niť a výplň; a pracuješ už jakoby s hotovým medvídkem (sic bez očí) a k látce niti a výplni se už nedostaneš.
V případě vytknutí parametrů do FooDependencies, vždy dostaneme košík nebo košatější třídu, která nám v návrhu chyběla.
Jestli sem to pochopil špatně tak mě prosím opravte.
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.