Úvod do dependency injection

před 4 lety od David Grudl  

Pamatujete si na svůj první program?

Netuším sice, v jakém jazyce byl napsaný, ale kdyby to bylo PHP 7, nejspíš by vypadal nějak takto:

function soucet(float $a, float $b): float
{
	return $a + $b;
}

echo soucet(23, 1); // vypíše 24 … teda ještě to ověřím … jo, vypíše

Trefil jsem se?

Je tam vlastně vše důležité. Že existují proměnné, že kód se člení do menších jednotek, což jsou kupříkladu funkce, že jim předáváme vstupní parametry a ony vracejí výsledek. Jistě, chybí podmínky a cykly, ale na ně došlo až ve vašem druhém programu 🙂 My zůstaneme ještě u toho prvního.

Že funkci předáme vstupní data a ona vrátí výsledek představuje základní kámen programování. Perfektně srozumitelný koncept, který chápe i naprostý začátečník. Používaný nejen ve všech dalších jazycích, ale i v ostatních oborech, jako je třeba matematika.

Funkce má svoji signaturu, kterou tvoří název, informace o vstupních parametrech a návratové hodnotě, a vnitřní implementaci. Jako uživatele nás zajímá signatura, o vnitřní implementaci nemusíme vůbec nic vědět.

Teď si představte, že by signatura funkce vypadala takto:

function soucet(float $x): float

Součet s jedním parametrem? To je divné… A co třeba takto?

function soucet(): float

Tak to už je opravdu hodně divné, že?

Jak se asi používá?

echo soucet(); // co asi vypíše?

Při pohledu do takového kódu bychom se všichni tvářili přesně takto.

Nejen, že by tomu začátečník vůbec nerozuměl, takovému kódu nerozumí ani šikovný programátor jako jste vy.

Asi si teď říkáte, proč takové hloupé příklady zmiňuji? Takhle by přece nikdo nikdy neprogramoval! To bohužel není pravda. Ve skutečnosti takhle programuje obrovská spousta chytrých lidí. A dělají to buď z neznalosti, nebo z lenosti programovat lépe, nebo proto, že je to naučili jiní. Třeba právě jejich framework, který jim říká, že je dobrý nápad psát funkce soucet() třeba takto:

function soucet(): float
{
	$a = Input::get('a');
	$b = Input::get('b');
	return $a + $b;
}

Tedy funkce soucet(), která si nějakým způsobem sama obstará sčítance. Nejen, že to zcela porušuje představu, kterou o funkcích máme, a že je její užití v kódu nesrozumitelné, ale také:

  • vytváří skryté vazby, které odhalíme až pohledem do implementace (a ta by nás přitom neměla zajímat)
  • je složité dohledat, odkud se sčítance vlastně berou
  • a je složité je změnit
  • vlastně netušíme, co vše může taková změna ovlivnit (neovlivní třeba funkci soucin()?)
  • přece není úkolem sčítací funkce si obstarávat vstupy, její zodpovědností je pouze sčítání

Ufff, rychle zpátky k původní srozumitelné podobě:

function soucet(float $a, float $b): float
{
	return $a + $b;
}

Krása. A pojďme si slíbit, že v Nette budeme vždy psát úplně všechny funkce a třídy právě tímto způsobem. Bez skrytých vazeb. Tak, aby byl kód srozumitelný nejen autorovi, ale i každému, kdo jej po něm bude číst. Aby vše bylo pochopitelné ze signatur a nemuseli jsme pátrat po skrytých tajemstvích v implementaci.

Pro tento způsob existuje označení a říká se mu dependency injection.

(„Dependency injection“ je něco úplně jiného, než „dependency injection container“. Tomu se budeme věnovat později.)

Dependency injection je prostá a přitom skvělá technika, která vám pomůže psát mnohem srozumitelnější a předvídatelnější kód.

Kód téměř vždy píšeme pro jiné: spolupracovníky, uživatele našich open source knihoven nebo o pár let starší sebe sama. Abychom předešli nepříjemným WTF momentům při jeho používání, je dobré dbát na srozumitelnost. Ať už v pojmenování identifikátorů, výřečnosti chybových zpráv nebo návrhu rozhraní tříd. A ke srozumitelnosti přidáme ještě předvídatelnost. Posuňme se od funkce soucet() k reálnějšímu příkladu:

$article = new Article;
$article->title = '10 Things You Need to Know About Losing Weight';
$article->content = 'Every year millions of people in ...';
$article->save();

Třída Article reprezentuje článek na blogu a metoda save() nám jej uloží. Kam? Asi do databázové tabulky. Skutečně? Co když ho uloží do souboru na disk? A pokud do databáze, tak do které? Ostré nebo testovací? Do PostgreSQL nebo do Redisu? Pod jakým uživatelem?

Museli bychom se podívat, jak je implementovaná metoda save(), abychom zjistili, kam se data ukládají. Zjistili bychom, že si nějakým způsobem obstarává databázové spojení. Museli bychom pátrat dál, kde se v kódu databázové spojení vytváří, a pak bychom teprve měli obrázek o tom, jak vše funguje.

class Article
{
	public function save(): void
	{
		$db = DB::connection(); // skrytá závislost
        $db->query(...);
	}
}

A teď si možná říkáte: vždyť je to přece jedno. Uloží se do databáze, tak se to má stát, víc mě nemusí zajímat. Proč do toho rýpeš?

Protože jsme si před chvíli slíbili, že budeme všechny funkce a třídy psát bez skrytých vazeb. Protože repozitář článků se skrytou vazbou na databázi je úplně stejně bizarní, jako soucet() bez sčítanců, jen si to možná nechceme přiznat. Proto ji opravím stejně, jako jsme opravili soucet(). Změníme skrytou závislost za přiznaný parametr:

class Article
{
	public function save(Database\Connection $db): void
	{
		$db->query(...);
	}
}

A najednou nejsou žádné otázky.

Budete-li psát třídu vyžadující ke své činnosti databázi, nevymýšlejte, odkud ji získat (tj. ze žádné statické metody, registru, singletonu, Facades atd.), ale nechte si ji předat. Třeba jako parametr konstruktoru nebo jiné metody. Přiznejte závislosti. Přiznejte je v API vaší třídy. Získáte srozumitelný a předvídatelný kód.

Související: dokumentace DI v Nette.

Komentáře (RSS)

  1. SOLID

    před 4 lety
  2. Bude v dalsich ukazkach i zminka o Factory? Proc by me jako vyvojare melo zajimat, do jakeho typu storage bude clanek ulozen, nemel bych predavat spise nejaky objekt, vytvoreny pres Factory, ktery implementuje napr. abstraktni Storage objekt?

    před 4 lety · replied [5] David Grudl
  3. @medvidek myslím si že hlavně article by měl odděděn od abstrakce deklarující uložitelnost nějakým rozhraním, a article samotný by vůbec neměl mít metodu save a tedy by object article vůbec nemělo zajímat že existuje nějakej storage a už vůbec by neměl vědět že jde o databázi

    před 4 lety · replied [5] David Grudl
  4. @medvidek, @ZbyRih – co se týče praxe, máte samozřejmě pravdu. Ale tento článek je o DI. A zdůrazňuje čitelnost, ne flexibilitu. Takže ok, závislost Article na Databaze je sice krutě přísná, ale je přiznaná, a to je to je třeba zdůraznit.

    před 4 lety · replied [5] David Grudl
  5. #2 medvidek#3 ZbyRih#4 Taco ten seriál k tomu postupně dospěje 🙂

    před 4 lety
  6. Prosím kdy se plánuje vydat pokračování?

    před 4 lety

Chcete-li odeslat komentář, přihlaste se