DI versus Service Locator

před 12 lety od David Grudl  

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