Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

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


Všechny části:

Komentáře

  1. Aleš Roubíček http://rarous.net/ #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 5 lety
  2. Daniel Milde http://blog.milde.cz #2

    avatar

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

    před 5 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 5 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 5 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 5 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 5 lety
  7. David Grudl http://davidgrudl.com #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 5 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 5 lety
  9. Mirek #9

    avatar

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

    před 5 lety | reagoval [10] mishak
  10. mishak http://mishak.net #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 5 lety

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