Přejít k navigační liště

Zdroják » Různé » Dependency Injection: motivace

Dependency Injection: motivace

Články Různé

Dependency Injection se stalo horkým tématem programátorských diskusí i na českých fórech. Někteří jej horlivě obhajují a jeho použitím vysvětlují různé konstrukce a doporučení, jiní jej považují třeba za „overkill“ – většinou z nepochopení. Vašek Purchart seznámí s DI všechny, co zatím tápou.

Návrhový vzor Dependency Injection (DI) slouží pro snížení závislostí mezi jednotlivými částmi systému. Jeho provedení vychází z obecnějšího návrhového vzoru Inversion of Control (IoC). Tyto dva pojmy jsou často zaměňovány – IoC je obecný princip, který upřednostňuje kontrolu zvenku objektů nad situací, kdy si objekt sám říká o věci v rámci svého kódu. Výsledkem je větší kontrola nad objekty a tento návrh zároveň umožňuje lepší znovupoužitelnost. DI je pojem, který poprvé představil Martin Fowler ve svém článku Inversion of Control Containers and the Dependency Injection pattern.

O seriálu

V tomto miniseriálu se postupně seznámíme s návrhovým vzorem Dependency Injection. Tento vzor sám o sobě nevypadá příliš složitě, důležitější je, jaké dopady má jeho důsledné dodržování na náš kód. Začneme se vzorovým příkladem, který by měl znázornit, proč je výhodné DI používat.

Příklad

Příkladem budiž prosté cachování dat – většinou je potřeba ho použít na několika místech v aplikaci a když už je jednou napsané, je vhodné ho sdílet i mezi více různými aplikacemi. Pravdou je, že je pravděpodobné, že tento problém za vás již vyřešil framework, se kterým pracujete, to ponechme stranou, zde jde pouze o ukázku principu.

Všechny příklady zde uváděné budou zapsány v PHP, ale návrhový vzor jako takový se samozřejmě na žádný konkrétní jazyk neváže. Uvedený kód berte pouze jako ilustrativní, důležité nejsou konkrétní implementace, pouze to, jak objekty skládáme dohromady a jak spolupracují.

Naivní implementace

Máme třídu FooService, která dělá nějakou činnost, o které si myslíme, že by bylo dobré její výsledky cachovat, protože výpočet je náročnější než prosté vyzvednutí již zpracovaných dat. Protože funkci cachování využijeme na více místech v projektu, vytvoříme si samostatnou třídu  Cache, která se bude o ukládání starat a bude zapouzdřovat operace s file systémem. Pro  cachování si vyhradíme složku cache v adresáři, který je označený konstantou TEMP_DIR.

class FooService {

    /** @var Cache */
    private $cache;

    public function __construct() {
        $this->cache = new Cache();
    }

    public function doSomethingUseful(Object $foo) {
        $index = ...
        if ($this->cache->exists($index)) {
            return $this->cache->read($index);
        } else {
            $result = $this->process($foo);
            $this->cache->write($index, $result);
            return $result;
        }
    }

    ...

}

class Cache {

    public function read($index) {
        return file_get_contents(unserialize(TEMP_DIR . '/cache/' . $index));
    }

    public function write($index, $data) {
        file_put_contents(serialize(TEMP_DIR . '/cache/' . $index));
    }

    public function exists($index) {
        return file_exists(TEMP_DIR . '/cache/' . $index);
    }

}

Výše uvedené řešení bude mít hned několik problémů, zejména:

  1. FooService je přímo závislá na konkrétním způsobu ukládání cache, nemůžeme jej tedy snadno vyměnit
  2. ve třídě Cache máme napevno napsané místo, do kterého budeme soubory ukládat

Zavedení interface

Chceme, aby FooService dokázala pracovat s libovolným způsobem ukládání dat, třídu Cache tedy proměníme na FileCache, která bude implementovat obecné rozhraní ICache. Druhý problém vyřešíme právě pomocí Dependency Injection – místo toho, aby třída FileCache rozhodovala, kam bude data ukládat, přenechá tuto povinnost tomu, kdo ji chce používat – to dá najevo požadováním předání cesty přes konstruktor.

class FooService {

    /** @var ICache */
    private $cache;

    public function __construct() {
        $this->cache = new FileCache(TEMP_DIR . '/cache');
    }

    public function doSomethingUseful(Object $foo) {
        $index = ...
        if ($this->cache->exists($index)) {
            return $this->cache->load($index);
        } else {
            $result = $this->process($foo);
            $this->cache->save($index, $result);
            return $result;
        }
    }

    ...

}

interface ICache {

    public function load($index);

    public function save($index, $data);

    public function exists($index);

}

class FileCache implements ICache {

    /** @var string */
    private $dir;

    public function __construct($dir) {
        $this->dir = $dir;
    }

    public function load($index) {
        return file_get_contents(unserialize($this->dir . '/' . $index));
    }

    public function save($index, $data) {
        file_put_contents(serialize($this->dir . '/' . $index));
    }

    public function exists($index) {
        return file_exists($this->dir . '/' . $index);
    }

}

V této podobě můžeme v konstruktoru třídy FooService do proměnné cache vložit jakoukoli implementaci. Jak je ale vidět z ukázky, stále natvrdo vytváříme instanci jedné konkrétní implementace. Momentálně tedy není příliš znovupoužitelná třída FooService – nemůžeme ji používat s různými úložišti. To se týká i určení cesty, kam se budou data ukládat v případě použití FileCache.

Ještě o krok dál

Zopakujeme tedy stejný krok ještě jednou – na parametry, které nechceme sami pevně určit, se zeptáme v konstruktoru. V tomto kroku změníme už jen konstruktor třídy FooService (to je dobré znamení).

class FooService {

    /** @var ICache */
    private $cache;

    public function __construct(ICache $cache) {
        $this->cache = $cache;
    }

    // zbytek stejný jako dřív

}

Z příkladu nám úplně zmizelo určení nějakých konkrétních hodnot, což je stav, ke kterému jsme se chtěli probojovat. Ani jedna ze tříd nemá žádné skryté závislosti (tedy kdybychom chtěli jít do důsledků, tak je závislostí i konkrétní file systém). Pokud bychom chtěli námi vytvořené třídy použít, mohlo by to vypadat nějak takhle:

$fooService = new FooService(new FileCache(TEMP_DIR . '/cache'));

Je důležité udělat ten správný krok – požadovat v konstruktoru implementaci ICache. Pokud bychom se v tomto kroku místo toho rozhodli, že chceme mít možnost zvenku FooService pouze konfigurovat cestu k úložišti (ať už přes konstruktor, nebo setter), tak bychom zaváděli do FooService kód který s ní přímo nesouvisí. FooService by pak vyžadovala parametr, jehož jediným smyslem by bylo jeho přeposlání do další třídy (FileCache). Kromě toho parametr s cestou využívá jen FileCache, je to tedy krok směrem pryč od používání interface ICache.

Poznámka k použitému příkladu: Bylo by vhodné oddělit logiku spojenou s cachováním (např. invalidace) a samotným ukládáním dat. Ve výsledku by tedy cache vyžadovala v konstruktoru předání libovolného úložiště.

Závěr

V tomto, úvodním dílu jsme si názorně ukázali na konkrétním příkladu, co nás k používání Dependency Injection vede. Postupné úpravy vedly k zobecnění tříd a snížení závislostí v systému. Pokud se budeme principu DI držet, dojdeme do fáze, kdy budeme mít kompletně oddělené instancování konkrétních implementací a jejich využívání v aplikačním kódu. V následujících dílech se budeme DI zabývat dál do hloubky a také probereme záležitosti, které s jeho používáním souvisí v širším měřítku.

Komentáře

Subscribe
Upozornit na
guest
94 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Radek Miček

Jaká je výhoda, když se místo staticky typovaného jazyka použije dynamicky typovaný jazyk a typy se píší do komentářů?

Landy

Nejde o typy v komentářich ale o uvolnění závislostí :)

Radek Miček

Tohle ale není odpověď na mou otázku ;-)

manik.cze

Nikde nic takového netvrdím, sám mám staticky typované jazyky radši, ale podle ankety je mezi čtenáři Zdrojáku nejpoužívanější PHP, tak jsem prostě použil tento zápis (ostatně je to v článku uvedeno).

Návrhový vzor jako takový s tím nemá nic společného, takže tímto bych rád tuto debatu ukončil.

Radek Miček

Díky za odpověď.

Návrhový vzor jako takový s tím nemá nic společného

Podle mě má – ve staticky typovaných jazycích se injector typicky řídí typem parametru, což v dynamicky typovaném jazyce nepůjde. Například ve staticky typovaném jazyce mohu mít generické rozhraní INeco<T>, kde se injector může řídit i podle typu T. Zatímco v dynamicky typovaném jazyce budu mít pouze INeco (parametrizovat typem to asi nepůjde), takže zmíněný scénář budu muset realizovat jinak.

Jan Tichý

A jak tenhle dotaz souvisí s tématem článku?

Radek Miček

1) Souvisí to s DI: Těžko můžete něco injectovat, aniž byste věděl typ potřebné instance. Což mě vede k závěru, že potřebujete typovou informaci, tudíž se ptám, proč nepoužít staticky typovaný jazyk?

2) Souvisí to i s kódem v článku, proč psát

/** @var ICache */
private $cache;

když mohu mít jen

private ICache cache;

Mj. statické typování přináší mnoho dalších výhod.

Landy

Asi proto, že DI neni něco co by vyžadovalo staticky typovaný jazyk. Jak jsem již psal je to hlavně o uvolnění závislostí a né o syntaxi jazyka. PHP se prostě vyjadřuje tak jak je uvedeno v článku a tím to končí. jediný rozdíl je v tom že já sám si v PHP musím hlídat jestli tam fakt mám instanci implementace daného rozhraní a nekontroluje to za mě kompilátor nebo to nevyvolá runtime error když se tam pokusím zapsat instanci jiného objektu

manik.cze

Ono to PHP dokonce i kontroluje – pomocí typové kontroly při předávání parametru do metody. Nicméně uvnitř objektu to v property samozřejmě už nefunguje. Vzhledem k tomu, že DI je o předávání závislostí do třídy, tak to by nás už ani nemělo zajímat.

kruppi

Na to jsem zapoměl :) přemýšlel jsem jen o té property :)

Aleš Roubíček

Až na to, že DI není o předávání závislostí do třídy.

1. Třída je pouze předpisem pro vytváření *objektů*.
2. DI stejně skvěle funguje i u metod nebo funkcí (u neobjektových jazyků). Je to obecný princip.

manik.cze

Ano Aleši, máš samozřejmě v obou bodech úplnou pravdu, kaji se.

Opravdový odborník :-)

Žádná, ale když chce někdo PHP, tak mu nic moc jiného nezbývá.

Radek Miček

Ještě dodám, že alternativou k DI je dynamic scoping.

Opravdový odborník :-)

Koukám, že bastlíři v PHP začínají pomalu objevovat to, co je v Javě už roky a na kvalitativně vyšší úrovni :-)

Tak jen přidám pár rad:

1) Volat nejdřív exists() a až když vrátí true volat read() je hloupost, protože musím provést hledání podle daného klíče dvakrát (ať už prohledávám hash tabulku nebo třeba ten souborový systém). Lepší je, když read() vrátí v případě neexistence null. Pokud by operace čtení neexistujícího byla výrazně dražší než zjištění existence bez čtení, mělo by se zjištění existence provést uvnitř implementace mezipaměti — ne v klientském kódu.

2) Vnitřek metody doSomethingUseful() je potřeba refaktorovat resp. přesunout do implementace mezipaměti, protože tenhle blok kódu (logika obsažená v tom if/else, zjišťování existence, vytažení hodnoty, uložení…) by se nám jinak opakoval všude, kde budeme cache používat. Ono se sice zdá, že je to jen pár řádků a že to nevadí… ale vadí.

3) Dávat do názvu rozhraní písmeno „I“ zavání tatarskou notací. Že jde o rozhraní je dané klíčovým slovem „interface“, není potřeba tuhle informaci duplikovat do názvu. Když už to chcete mermomocí odlišit, tak je lepší konvence mít rozhraní bez předpony/přípony a k implementacím doplnit na konec „Impl“ (i když to je taky nadbytečné, protože FileCache je prostě souborová implementace Cache).

4) Hlavní kouzlo DI je v tom, že se o něj může postarat kontejner a jeho konfigurace není potřeba psát něco natvrdo do kódu a předávat pomocí parametrů konstruktoru. Má PHP něco takového?

5) Nezapomínat na bezpečnost — chtělo by to kontrolovat, co se předává jako $index. Souborová mezipaměť by si měla hlídat, že poskytuje jen soubory z daného adresáře a žádné jiné — aby nešlo vytáhnout libovolný soubor z disku.

fos4

To, že vyjde o DI článek v PHP nyní neznamená ještě, že v PHP to nebylo už dříve, některé frameworky to už mají dlouho :-)

Jinak bych do detailu neřešil kód ono tam těch chyb je více (např. file_get_conten­ts(unserialize(TEM­P_DIR . ‚/cache/‘ . $index)); ), ale pro názornost a úvod do DI to bohatě stačí.

Jinak tady jsou celkem pěkné další články o DI:
Seriál od Fabiena:
http://fabien.potencier.org/article/11/what-is-dependency-injection
Martin Fowler:
http://www.martinfowler.com/articles/injection.html

manik.cze

ad 1,2,5) Cituji z článku: „Všechny příklady zde uváděné budou zapsány v PHP, ale návrhový vzor jako takový se samozřejmě na žádný konkrétní jazyk neváže. Uvedený kód berte pouze jako ilustrativní, důležité nejsou konkrétní implementace, pouze to, jak objekty skládáme dohromady a jak spolupracují.“

ad 3) Odlišené pouze pro přehlednost v rámci článku

ad 4) Toto je úvodní díl s názvem motivace, pokročilejším záležitostem jako jsou kontejnery se budu věnovat v některém s dalších dílů. Seriál je rozdělen protože všechny související věci by se do jednoho článku nevešly, kromě toho když DI ještě někdo nepoužil, tak není dobré na něj hned jít s kontejnerem – samotné DI nic takového nevyžaduje.

Děkuji za názor opravdového odborníka ;)

Přijde mi, že jste se sem přišel pouze vyzvracet, byť s Vašimi připomínkami lze pouze souhlasit (až na to I, to je věc zvyku a konvence).

Takže: Jak tohle všechno souvisí s úvodem do Dependency Injection? Proč by měl úvod řešit věci, které s ním vůbec nesouvisí?

R!

Ad 1)
Co kdyz budu v cachi ukladat prave danou NULL hodnotu ?

mark8468464

Zřejmě by se v takovém případě spíš měla vyhodit výjimka než návratová hodnota null …

Tharos

3) Když už to někdo chce mermomocí rozlišit, v čem je přidávání „Impl“ k názvu implementací lepší? Mně to přijde jako notace ještě tatarštější :). Přestože to doporučuje například Robert „Uncle Bob“ Martin ve své knize Čistý kód /kterou mi přijde – bez urážky – že jste právě dočetl a vydal jsem se rychle do vod internetu rozsévat právě načerpaná moudra :)/, nevidím žádnou výhodu v tom mít namísto jednoho rozhraní zapleveleného písmenkem „I“ zaplevelených rovnou několik tříd mnohem delším přídavkem „Impl“.

Ptám se úplně vážně, v čem je to lepší? Očekávám nějaký velký přínos, když už jste to měl potřebu napsat jako offtopic komentář do článku o něčem úplně jiném. Peace. :)

kruppi

Já se přiznám :) já jí právě dočet ale teda ničeho podobnýho sem se tam nedočet. Možná proto, že jsem nedával ani tak pozor na konvence jazyka jako na rady jak psát čistější kód :)

Opravdový odborník :-)

Díky za otázku.

Knihu Čistý kód jsem nečetl. Je dobrá? Četl jsem např. Dokonalý kód, ta se mi docela líbila. Ale človek se toho stejně nejvíc naučí procvičováním v praxi a od zkušenějších kolegů, skutečných mistrů.

Dělám třicet let bankovní aplikace v Javě, tak snad můj příspěvek nikoho neurazil. Myslím to s vámi dobře. Je přeci hezké povídat si o programování, i když to přímo nesouvisí s tématem článku, ne?

Co se týče té konvence: ono je to jen zdánlivé, že rozhraní je jedno a implementací je hodně a tudíž to „Impl“ zapleveluje více než jedno „I“. Jenže ve skutečnosti je to tak, že když už rozrhaní definujeme, pracujeme pak v klientském kódu (témeř výhradně) s ním a proměnné typu XXXImpl se nám téměř nikde nevyskytují. Na to si člověk musí přijít sám léty praxe, nebo mu to někdo zkušenější poradí — tady to máte na Internetu zadarmo, tak berte dokud dávám :-)

A druhým důvodem pro tuto konvenci je tento: kromě běžných tříd a rozhraní máme i abstraktní třídy, což je jakýsi mezistupeň (vulgárně řečeno) a my můžeme v průběhu vývoje zjistit, že místo rozhraní nám bude více vyhovovat abstraktní třída (nebo naopak z abstraktní třídy budeme chtít udělat rozhraní). A pak bychom museli všechny výskyty přejmenovat (odstranit nebo přidat „I“), co by bylo dost práce, ale třeba i peněz, protože na tom našem rozhraní mohl už začít stavět někdo jiný a najednou by i on musel přejmenovávat. Takže když už si nějak ty třídy a rozhraní potřebujete poznačovat, používejte „Impl“ místo „I“, ušetří vám to náklady a váš vývoj bude radostnější.

Děláme to tak my Javisté — zatímco třeba patlalové v C# používají to „I“ (oni vůbec dělají hodně věcí natruc obráceně, aby se odlišili, ale většinou to zmatlají a jejich programy stojí za starou bačkoru).

Opravdový odborník :-)

P.S. oprava:

sed s/“A druhým důvodem“/“A druhý důvod“/g

8ecd0389dc24441­1e8993d582bd0a­e77

V čem ušetřím, pokud budu mít rozhraní, které musím implementovat třídě, přepisovat na abstraktní třídu, ze které musím dědit? A nebo i klidně naopak. Myslím si, že jedno písmenko mě nezachrání :)

Opravdový odborník :-)

Máme:
— jedno rozhraní
— několik jeho implementací (řádově jednotky)
— spousty použití tohoto rozhraní (klidně stovky nebo i tisíce výskytů) v klientském kódu (a ten mohl psát i někdo jiný)

Takže jestliže změníme rozhraní na abstrantní třídu nebo obráceně, nebude potřeba měnit ten klientský kód, kterého je nejvíc, ale změníme jen těch pár výskytů (jednotky kusů).

30 let programujete v jazyku, který byl vydán před 16 lety? To jste ho asi vymyslel, že?

Jan Kodera

billy joy se asi naučil česky a rozhodl se realizovat se skrze komentaře na zdrojáku :)

mark8468464

Hele zase to nepřehánějte, a zvolněte trochu s těmi osobními útoky … jeho komentář se mi zdá dost smysluplný, kašlete na to že se chlubí praxí a vnímejte víc podstatu toho co píše, ano? Děkuji.

Radek Miček

…zatímco třeba patlalové v C# používají to „I“…

Je to pouze moje spekulace, ale myslím si, že tuhle konvenci Microsoft převzal z technologie COM, kterou .NET do jisté míry nahradil.

Martin Soušek

V Microsoftu jsou prapodivní odedávna.

Kde jinde by mohli vynalézt tuhle hrůzu: http://en.wikipedia.org/wiki/Hungarian_notation ? Bylo tím prolezlé celé Windows api a MFC

A ty vaše I a Impl jsou jen dozvuky tohoto nesmyslu.

Opravdový odborník :-)

jj, jsou to dozvuky tohoto nesmyslu — proto jsem taky výše psal, že je nejlepší je nijak neoznačovat — jsou totiž označené klíčovým slovem „class“ nebo „interface“ stejně jako typy proměnných jsou dané slovy „int“, „String“ atd., není potřeba tuto informaci duplikovat do názvu proměnné nebo třídy/rozhraní. Ale když už si to někdo označovat musí, tak je lepší konvence „Impl“ než „I“ a snad se mi i podařilo vysvětlit proč :-)

Tharos

Nepodařilo. :) Navrhuji konec siláckých řečích o odprogramovaných dekádách a přejděme k činnu – ukažte kód, který ty Vaše výhody demonstruje (třeba „Impl“ vs „I“, ale klidně i jiné).

Ten kód nemusí být zrovna úplně výstavní, PHP programátoři jistě budou shovívaví. Klidně vytáhněte ze šuplíku i starší Java kousek, třeba nějaký vyzrálý dvacetiletý ;).

8488307681665­f3dc017ebcab0c4cd7b17­33e102 HOUK :)

Opravdový odborník :-)

„Impl“ samo o sobě žádnou výhodu nepředstavuje — je to pouze menší zlo ve srovnání s „I“. Jak už jsem tu psal, nejlepší je nepoužívat ani jedno, protože všechna potřebná informace je obsažena v tom klíčovém slovu „interface“ resp. „class“ a je nadbytečné (ba škodlivé) ji ještě duplikovat do názvu třídy/rozhraní. Více k tomuto tématu už nemám co říct (a víc prostoru si ani jedna hloupá konvence nezaslouží). Pokud si chcete ještě povídat, zkuste nadhodit jiné zajímavější téma :-)

anonym

Mne by přesto zajímala jedna věc: pokud pro rozhraní a třídy používáte pojmenování bez prefixu a suffixu, často vám jistě vznikají stejné názvy. Obzvláště právě u DI a obecně programování přes rozhraní.

Jak toto řešíte? Oddělujete rozhraní a třídy do jiného jmenného prostoru?

Opravdový odborník :-)

Rozhraní používáme v případě, že připadá v úvahu více implementací (ne třeba hned v první verzi, ale výhledově počítáme s tím, že jich bude více). Mít právě jednu implementaci k jednomu rozhraní nedává valný smysl.

Máme tedy více implementací a ty se od sebe nějak liší – tudíž se i jinak jmenují. Když zůstaneme u příkladu z článku: budeme mít jednou souborovou Cache jednou paměťovou a jednou databázovou – každá se bude jmenovat jinak a rozhraní se bude jmenovat třeba jen Cache. Tudíž k žádným konfliktům jmen docházet nebude. Není tedy nutné používat ani jiné jmenné prostory.

www.google.com

Díky za odpověď. Právě jsem nad tím dumal a vzpomněl si, že jsem se pár měsíců zpět na to někoho (vás) ptal :)

Kvalitu programu jmenné konvence rozhodně nedělají :)

Ačkoli mám Javu rád, prefixovat rozhraní s I je mi bližší. Nerozlišovat jeho název od tříd postačuje do té doby, pokud je přijímám jako parametry metod – pak mě nezajímá, zdali se jedná o rozhraní nebo jeho implementaci.

Často se mi ale stává, že si někde všimnu používaného názvu, chci ho instanciovat a IDE mi udělá obrovskou nudli kódu s anonymní třídou – protože jsem mu řekl, že chci instanciovat rozhraní.

Nejmarkantnější je to u Stack (třída) a Queue (rozhraní). Přitom podle názvosloví jsou na stejné úrovni.

Zaboj Campula

Obávám se, že tvrzení „Dělám třicet let bankovní aplikace v Javě“ se dá jen stěží věřit. Nejsem opravdový odborník takže nedokážu posoudit zda je vhodnější mít interface s předponou I nebo implementaci s příponou _impl, nicméně vaše tvrzení, o kterém lze s úspěchem pochybovat snižuje důvěryhodnost ostatních informací, které nám s velkou laskovostí předkládáte.

dusanmsk

Teda precital som si Tvoj komentar este raz a musim povedat, ze nevychadzam z udivu. Clovece, ty si mag. „30 rokov bankove aplikacie v Jave“, padam pod stol a zacinam rotovat.

Odhliadnuc od toho, ze pisanie bankovych aplikacii nie je nic, cim sa da chvalit ( bohuzial mam tu cest, uz chapem tie kecy kolegov z minulych firiem o cvicenych opiciach v bankach, sam som sa jednou takou opicou stal a radsej sa sposobom obzivy moc nechvalim ), tak pisat ich v jazyku, ktory v tej dobe este neexistoval, je uctyhodne. Java je na takyto druh aplikacii pouzitelna ( a skutocne je to tak? ) par rokov, nech nezerem, maximalne 6.

C# zas tak odlisny nie je, nech platformu microsoftu neznasam ako chcem, v jazyku a jeho moznostiach ako takom nie su zasadne rozdiely. Najvacsi rozdiel je akurat tak vendor/platform lock-in.

Inak pozdrav pani ucitelku v skolke. A ked budes v Jave programovat 80 rokov, tak sa sem vrat, ok?

arnold

Vy ste asi ešte nevidel lambda metódy a v C# 5.0 async programovanie. To je ten najväčší rozdiel. Takže najprv si naštudujte niečo o jazykoch a až potom píšte veľkohubé vyhlásenia.

msk

„Impl“ neznasam. Vzdy ked vidim takto pomenovanu triedu, lamal by som ruky.

mark8468464

a nejake zduvodneni by nebylo ? takhle je to jen nic nerikajici vykrik do tmy …

harvejs

ako sa uz pise v rozbiehajucej sa diskusii, ide cisto a nastrel problematiky, ale velka vdaka za to ze sa zacinaju riesit veci ako navrhove vzory atd. drzim palce a tesim sa na dalsie diely!

PS: neda mi este k odbornikovmu bodu c. 1 :) ak uz ideme do takychto puntickarskych detailov (co samozrejme vobec nebolo cielom tohto clanku), ak ocakavam od funkcie objekt nemozem vratit null, ale hodit exception. to len tak na okraj :)

Radek Miček

Tohle ale není detail. Mezi testem a čtením může hodnota z cache vypadnout a máte z toho bug.

Pokud do cache záměrně neukládám null, pak s tímto řešením není žádný problém. Pokud ano, mohu vrácený objekt zabalit do typu Some/ Maybe známého z jiných jazyků.

Ondřej Mirtes

Článek vůbec není o těchto implementačních detailech.

zomp

Samozřejmě že ne, je na to ale dobré upozornit – když se návrhové vzory implementují špatně, pěkně to dokáže situaci zhoršit (regrese ve výkonu, nesrozumitelný kód, menší znovupoužitel­nost…).

Andrew

To ale přece vůbec nevadí. Když člověk nemá nic k tématu, nabízí se oblíbené fráze „tohle máme v brainfucku už roky“, „v PHP programují jenom lamy“, případně se najde nějaká drobnost (bonusové body jsou za to, když nesouvisí s tématem článku) a na ní se dokáže, že autor je patlínek, který vlastně vůbec neumí programovat. Jo, zavděčit se je těžké :)

P.S.: Těším se na další díl ;)

v6ak

To je na jednu stranu pravda, ale na druhou stranu, nejsem pro utvrzování nováčků v některých chybách. Je to podobné jako <?php echo $_GET[‚name‘]; ?> v nejedné učebnici PHP – sice je asi cílem ukázat něco jiného, ale mnoho nováčků se toho může chytit a používat to, protože kniha je pro ně určitou autoritou. V tomto případě je to relativně zřejmé, ale u některých útoků (třeba CSRF a hrátky se sessions) až tak ne.

František Kučera

Přesně tak. Ono je sice nevhodné zneužívat návratové hodnoty pro řešení výjimečných stavů (např. vracet -1, -2, -3 atd. a podle toho pak luštit, co se vlastně stalo a v nevýjimečných případech vracet kladná čísla – skutečné hodnoty), v takovém případě je správné vyhodit výjimku.

Jenže vracení null bych za takové zneužití nepovažoval – jednak null dobře vystihuje, co se stalo (chybějící* hodnota je null hodnota) a jednak je to běžná konvence. Navíc tohle chování musí být popsané v dokumentaci metody, takže každý ví, co od ní má čekat (např. tady by bylo napsané: „vrací hodnotu z cache nebo null, pokud hodnota ještě nebyla načtena“). Navíc to, že hodnota chybí není nijak výjimečný stav – naopak je to často stav normální (např. po startu programu nebo u položek, ke kterým se nepřistupuje příliš často), takže vyhazovat výjimku není úplně vhodné.

Nicméně pokud by někdo potřeboval rozlišovat, zda je hodnota skutečně null nebo pouze nebyla zatím do cache načtena, tak je možné to přepsat s použitím těch výjimek (ale není to preferovaná varianta). Každopádně má pravdu Odborník – je potřeba, aby to byla atomická operace, ne exists() a read() zvlášť – jednak kvůli výkonu a jednak kvůli tomu, že se hodnota může vypršet a při read() dojde k chybě, ačkoli jsme podle exists() očekávali, že dostaneme hodnotu.

*) Trochu složitější je to při vracení kolekcí – některé týmy mají pravidlo, že místo kolekcí se nesmí vracet null a místo toho se musí vrátit prázdná kolekce. Jinde se naopak null místo kolekce vracet může a má to nějak definovaný význam. Tady záleží na konvenci a pravidlech daného týmu/projektu. Ale to už zabíháme do zbytečných detailů.

uživatel

že korektor tady asi trochu zaspal :)

kruppi

A tady asi taky ;)

Fanoušek A. Denta

Příklad v článku neumí rošádu!

Tím je vše bezpředmětné!!

Chvíli mi to trvalo, ale pak jsem si vzpomněl :-) On je to takový interní humor, podle hodnocení tvého komentáře to vypadá, že ho většina lidí nepochopila. Takže pro ostatní: STFW :-)

Lopata

Ale braní mimochodem už zvládá, takže je to v pohodě.

.

To je teda zase pecka.

Aleš Roubíček

Injektování cache do service je v první řadě smell. Tohle je ideální místo na užití vzoru dekorátor.

manik.cze

Když jsem vymýšlel příklad, tak jsem tak mě bohužel nenapadlo nic moc, kde by se nemuselo vlastně vysvětlovat o co jde a ostatní by to pochopili. Toho, co říkáš jsem si byl vědom, ale rozhodl jsem se to risknout i přesto, že mi to tu může někdo omlátit o hlavu. Proto jsem se snažil do třídy FooService napsat pouze minimum informací, v zásadě ona může být tou „vnější“ cachovací vrstvou, která teprve v metodě process volá původní třídu. Je pravdou, že decorator by byl pro implementaci cache vhodnější.

Díky za oba přínosné komentáře.

Jan Tichý

Aleši, díky za připomínku k věci! Než Vašek poslal svůj první díl do redakce, tak jsme spolu přesně tohle řešili, rozhodnutí ukazovat to zrovna na cache bylo nakonec motivováno hlavně průhledností a snadnou pochopitelností tohohle příkladu pro lidi, kteří ještě nikdy o DI nezavadili…

Fanoušek A. Denta

Franto Kučero, omlouvám se, ale netuším, o čem píšete :-) Jen jsem chtěl adentovi udělat radost, když si na to na twitteru stěžoval :-) Popravdě netuším ani, co je to v programování rošáda :D

Fanoušek A. Denta

Aha, díky :-)

Leoš

DI ve staticky typovaných jazycích prostě funguje lépe, právě proto že DI framework může pracovat s těmi statickými typy a podle nich se rozhodovat co kde „injectne“. Projděte si například základy Google Guice a pochopíte.

Tharos

Žeby další Javista s praxí kolem 30 let ;)?

Chápeme a nemusíme si nic procházet. Proč ten konfrontační tón? Zde někdo tvrdí, že DI funguje lépe v dynamicky typovaných jazycích?

Tady jde o to, že při rozhodování, jaký jazyk na daný projekt použiji, je typovost relativně dost nepodstatná. A mám-li spadeno na PHP, které má množství výhod (viz i jeho popularita), mám jej zavrhnout proto, že je dynamicky typované a v Javě by DI fungovalo o trochu lépe? Nebo jaké myšlenkové pochody se nám tu snažíte vštípit?

Na podobné komentáře jsem asi krátký… :) Nechápu pohnutky, jaké autory vedou k jejich napsání.

Jirka V.

Myslím si, že podobné jako mají Svědkové Jehovovi k nedělnímu obcházení domácností. Musí nás – pobloudilé to ovce obrátit na správnou (svou) víru a za to přijdou do nebe.

asdasd

Taky mi to tak někdy připadá. Diskuze na téma „staticky typované jazyky jsou lepší než dynamicky typované“, „Java je lepší než C#“, „Ruby je lepší než PHP“, „levá ruka je při honění lepší než pravá“, jsou většinou o hovně.

stydim se

Ja honim pravou ! Lorem Ipsum is simply dummy text of the printing and typesetting industry. Levou honej jen bastliri a patlaci kodu.

stydim se

Mimochodem uz s ni honim 30 let, takze jakakoliv dalsi diskuze je bezpredmetna.

asdasd

Tak to 30 let honíš v (sperma)bance a je ti 16, ne? Snad můj příspěvek nikoho neurazil. Myslím to s vámi dobře. Je přeci hezké povídat si o honění, i když to přímo nesouvisí s tématem článku, ne? :-D

David Grudl

Tak to teď přijde velké překvapení: žádné černobílé dělení na statické a dynamické jazyky neexistuje. Kam zařadit PHP, které má statické typování tříd předávaných metodou (šok, že?), kam zařadit C# 4 s jeho dynamickým typováním? A jak to souvisí s DI se raději ani neptám ;-)

Radek Miček

má statické typování tříd předávaných metodou (šok, že?)

Řekl bych, že nemá, neboť se typy kontrolují až za běhu programu.

kam zařadit C# 4 s jeho dynamickým typováním?

C# je staticky typovaný – v době kompilace rozlišuje více než 1 typ.

A jak to souvisí s DI se raději ani neptám

Mé poznámky se vztahovaly ke staticky typovaným jazykům s bohatším typovým systémem (osobně nemám rád dělení na staticky a dynamicky typované, protože to druhé je první případ toho prvního).

Konkrétně mi tedy řekněte, jak provedete DI pro funkci s hlavičkou f(a, b).

Radek Miček

s/první případ/speciální případ/

kruppi

Pokud funkce f zavisi svou funkci na objektu a tim ze ho predam jako parametr provedu DI

Radek Miček

Ano, a jaký objekt tam předáte?

David Grudl

Pošlu tam objekt, který tam chci mít. Přece typ neurčuje, jaký objekt tam mám dát. Pokud mám funkci copy(File $from, File $to), tak sice vím typ, ale nic to neřeší, setsakramentsky záleží na tom, abych objekty uvedl v zamýšleném pořadí. Pokud jsi v tomhle viděl výhodu typovosti, tak pro samé kontejnery nevidíš DI. Podstata je skutečně někde jinde.

Nehledě na to, že v PHP lze taky zapsat copy(File $from, File $to) a funguje to dle očekávání včetně reflexe.

manik.cze

„pro samé kontejnery nevidíš DI“

krásně řečeno

Radek Miček

Nehledě na to, že v PHP lze taky zapsat copy(File $from, File $to) a funguje to dle očekávání včetně reflexe.

Nemáte tam parametrizovaný typ, např. pro INeco<T> to už fungovat nebude (například pro kolekci prvků typu T).

Netvrdím, že nemůžete DI dělat v PHP, Pythonu apod., ale jsem přesvědčen, že to půjde lépe v jazycích s bohatším typovým systémem – třeba kvůli zmíněnému kontejneru, který má v těchto jazycích mnohem více informací a tím pádem mnohem větší potenciál pro automatizaci.

OT: ostatně, co dnes nejde lépe v jazycích s bohatším typovým systémem.

Opravdový odborník :-)

Nějak mezi řádky vašich příspěvků cítím, že máte na mysli Spring. Je tomu tak? Pokud ano, tak jen drobné varování:

Spring je lákavý a za určitých okolností užitečný, ale… místy se z toho stává šamanismus. Je tam příliš mnoho automatizace a snahy přemýšlet místo programátora (resp. programátor toho napíše méně a stroj si má domyslet ten zbytek). Spring zdaleka není dokonalý software (jemně řečeno). Někdy bohužel injektuje něco jiného než programátor chce (nebo odmítá injektovat úplně) a přesvědčit ho je takřka nemožné. 8ecd0389dc24441­1e8993d582bd0a­e77 Třeba to časem opraví, ale zatím to není úplně ono. Proto dávám přednost raději standardnějším prostředkům Javy EE, kde má člověk věci víc pod kontrolou a ani tam není moc psaní navíc (spíš naopak).

On je to problém s příliš volnou vazbou (bohužel velký problém dneska v SW inženýrství), vyhodnocování až za běhu a nejistým nepředvídatelým výsledkem. Tak vznikají křehké a snadno se rozpadající se systémy (ačkoli záměr byl přesně opačný — volnější vazby měly dodat systému pružnost).

Je to velmi podobné tomu sporu mezi staticky a dynamicky typovanými jazyky (resp. silně a slabě, pozor, není to totéž). Věci, které fungují tak nějak samy od sebe jsou někdy příjemné, ale často nespolehlivé — v opravdových programech je lepší se jim vyhnout (to může být i to injektování na základě typu, kde si stroj domýšlí, co by měl injektovat a ne vždy je to jednoznačné). Když se vrátíme k tématu článku: důležité je, že některé věci nejsou zadrátované uvnitř, ale jsou předávány zvenku — ale jestli to předávání bude nějak automatizované a kouzelnické, nebo řízené ručně, precizně je už druhá věc.

Jinak s vámi ale souhlasím ohledně těch dynamických/sta­tických jazyků, např. statické typování ušetří část testu, protože řadu věcí není potřeba testovat: roli některých testů přebírá kompilátor, který má dvě výhody — pracuje zadarmo (nemusíme ho psát, narozdíl od testů, použijeme hotový) a nemůžeme na něj zapomenout nebo se na něj zapomenout (zkompilovat program před spuštěním prostě musíme).

Radek Miček

Když programuji v C#, používám Ninject, dříve jsem používal Autofac. Spring nepoužívám.

Sid

kde Spring injektuje nieco co programatoror nechce? nejaky priklad by sa nasiel – alebo je to typu jedna pani povedala?

Radek Miček

Stejně tak Vám budu tvrdit, že se v jazycích s bohatším typovými systémem lépe testuje:

1) některé vlastnosti testovat nemusím, protože je garantuje typový systém

2) mohu využít automatického generování testovacích dat podle typu

A Vy mi můžete opáčit, že pro samé generování dat nevidím testování. Ale já vidím DI i testování.

Radek Miček

Přece typ neurčuje, jaký objekt tam mám dát.

Někdy ano.

Jan Tichý

Radku, prozraďte mi, o co Vám jde? Kam míříte, co se nám v kontextu článku snažíte říct? Že ve staticky typovaných jazycích se injektování implementuje o drobet lépe, než v dynamických jazycích? OK, tohle sdělení už jsme od Vás asi všichni dávno pochopili. Co dál? Že nemá cenu snažit se o DI v dynamických jazycích? To asi ne, že jo. Tak o co vám tady v té Vaší onanii jde?

Radek Miček

Reagoval jsem na komentář pana Grudla. Konkrétně na větu

A jak to souvisí s DI se raději ani neptám

Vadí Vám to?

kruppi

v DI jde o to že pokud někdo nebo něco potřebuje nějaký jiný prostředek k tomu aby provedl svou funkci, tak si ten prostředek nevytvoří ale dostane ho. Nevím co s tím má společného typovost jazyka.

Radek Miček

Nevím co s tím má společného typovost jazyka.

Už to tu padlo asi třikrát.

asdasd

No a je to opravdu tak zásadní a nepřekonatelný rozdíl? Pro mě teda ne.

martin

trochu sa zasekavate na implementacnych detailoch tam, kde ini riesia princip

v6ak

Tento příklad ukazuje, že to v některých případech nelze vyřešit jen na základě typu. (Ještě viditelnější by to bylo u Stringu.) Ale neříká, že je to vždy blbost. Mám-li vycházet z předchozího případu, bude to trošku umělé, ale ještě to půjde (jazyk: Scala):
copy(from: File, to: File, implicit copyingStrategy: CopyingStrategy)
Parametr copyingStrategy je možné neuvést, pak se kompilátor pokusí vyhledat v aktuálním scope (tedy i v proměnných/funkcích viditelných jen díky importu) nějakou vyhovující proměnnou nebo funkci, která je označena slovem implicit.

Lepší příklad by mohl být s databázovým připojením a konfiguracemi. Ale v zásadě jde o to stejné, jen to není tak umělé.

Že by to šlo i v dynamicky typovaném jazyce? Určitě ano, ale:
* Ještě jsem to neviděl.
* Mohl by to být takový slowdown, že by se tomu programátoři radši vyhýbali. Ve staticky typovaných jazycích je tato režie jen u kompilátoru, za běhu není rozpoznatelné, zda to programátor chtěl mít pohodlnější.
* Optimalizovat by to zcela určitě šlo, ale nejspíš by to stálo spoustu úsilí a výkon by pak byl trochu magií – nevinná úprava by mohla nečekaně srazit výkon.

v6ak

U PHP je to spíše cukr pro dynamickou kontrolu, který je navíc viditelná přes Reflection apod. Pak mi totiž PHP nebrání, abych tomu přiřadil něco jiného a vše je kontrolováno až za běhu. Mezi následujícími ukázkami kódu není v PHP až takový rozdíl:

http://ideone.com/MgjEk

http://ideone.com/VM9ks

Ve staticky typovaných jazycích (C#, Java, Scala, …) by tam byl podstatný rozdíl.

C# je už lepší příklad. Podobně by se dala uvést Java/Scala a Reflection API, ačkoli to je spíše vlastnost standardní knihovny. Ale pořád tu vidím jeden podstatný rozdíl: Javu, Scalu, C# etc. lze použít jako staticky typovaný jazyk. PHP bez nějaké nadstavby ne. Lze v něm psát, jako by bylo typované staticky (to je velmi časté), ale standardně (bez úpravy jazyka) budou všechny kontroly probíhat dynamicky, aspoň z pohledu programátora (optimalizátor je může provést dříve).

Ale souhlasím, že je problém udělat dvě škatulky jazyků a do jedné dát staticky typované a do druhé dynamicky typované. Takový Prolog by se škatulkoval už hůře, protože na dynamicky typovaný jazyk je (z mého podledu) příliš staticky, ale na staticky typovaný jazyk příliš dynamický.

Pavel Šimerda (pavlix)

Už když jsem viděl na začátku článku to o „nepochopení“, tak jsem si říkal, že bude mít autor dost, co dělat, aby mě přesvědčil, že právě on je ten, který chápe.

Odhlédnu-li od toho, že mě nepřesvědčil… DI tak jak je popsané v tomto článku mi přijde jako pojmenování pro něco, co alespoň trochu slušní programátoři považujou za samozřejmost. Nehledě na to, že v tom nevidím žádnou injekci.

Jestli to správně chápu, tak autor článku napsal, že DI spočívá v tom, že konstruktor Cache má jako parametr, kde ta cache bude, a konstruktor nějaké jiné třídy má jako parametr objekt cache. Prostě triviální pospojování objektů.

v6ak

„DI tak jak je popsané v tomto článku mi přijde jako pojmenování pro něco, co alespoň trochu slušní programátoři považujou za samozřejmost.“

Celkem souhlasím. Já taky k tomu přišel „přirozenou cestou“ – říkal jsem si, že ony závislosti se mi tam berou nějak „magicky“ a že používání (de facto globálních) cfg souborů není ideální (např. není možné použít tu třídu pro dvě různé DB), tak jsem k tomu taky přišel.

Ale na druhou stranu, návrhové vzory bývají obvykle jednoduché. Když si vezmu třeba Factory, Strategy nebo i onen nadužívaný Singleton, jejich implementace bývá obvykle na pár řádků.

Dokonce bych řekl, že Design Patterns tu možná nejsou k tomu, abychom se podle nich učili. Ty tu jsou od toho, abychom mohli snadno popsat opakované vzory – abychom to nemuseli pokaždé znovu popisovat. Jinými slovy, DRY v dokumantaci a komunikaci.

„v tom nevidím žádnou injekci.“
DI je „vložení závslostí“ – ty vkládám například v konstruktoru. Taky jsem měl zpočátku problém pochopit ten název. Ale spíš se mi nelíbí pojem IoC (Inversion of Control), protože zde nevidím inverzi. Spíš bych za inverzi považoval nepoužití IoC.

„Prostě triviální pospojování objektů.“
Jak jsem již psal, návrhové vzory nemají za účel být něčím složitým. Komplikovaný návrhový vzor něčím smrdí a v lepším případě vy si zasloužil rozdělení na více malých.

Aleš Roubíček

inverze řízení znamená, že objekt si vytváření závislostí neřídí sám, ale nechává to na někom jiném. Zde je ta inverze. Bezparametrický konstruktor, který vytváří závislosti se otočí a pouze je nadeklaruje jako parametry.

defk

k teme DI a tomu, ako napomaha unit testingu a podobnym veciam mozem odporucit prednasky Miska Heveryho a Clean Code Talks z Googlu na
http://www.youtube.com/results?search_query=misko+hevery
http://misko.hevery.com/
http://googletesting.blogspot.com/

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.