Na navigaci | Klávesové zkratky

DI versus Service Locator

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é.


Komentáře

  1. Aleš Roubíček #1

    avatar

    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.).

    před 12 lety
  2. Daniel Milde #2

    avatar

    Už to zaznělo na Twitteru, takže jen zopakuji: Použitím FooDependencies porušujeme Demeterův zákon.

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

    avatar

    #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.

    před 12 lety | reagoval [4] Jiří Landsman
  4. Jiří Landsman #4

    avatar

    #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

    před 12 lety | reagoval [5] Mirek [7] David Grudl
  5. Mirek #5

    avatar

    #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!“

    před 12 lety
  6. 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!

    před 12 lety
  7. David Grudl #7

    avatar

    #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,

    Je trochu škoda že je vidět pouze konstruktor

    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.

    před 12 lety | reagoval [8] Jiří Landsman [9] Mirek
  8. Jiří Landsman #8

    avatar

    #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.

    před 12 lety
  9. Mirek #9

    avatar

    #7 Davide Grudle, prosím, k čemu je dobrý předpoklad immutability?

    před 12 lety | reagoval [10] mishak
  10. mishak #10

    avatar

    #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.

    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í.