34 komentářů k článku Dependency Injection: předávání závislostí:

  1. smilelover

    SL neni IoC ani DI!

    Service Locator nema s IoC nic spolecneho, protoze porusuje zakladni premisu danou primo v samotnem nazvu! Nejde o inversion of control, kontrolu si ponechavam ja — ja si zadam o zavislost misto aby mi byla dodana pri stavbe objektoveho grafu. Stejne tak zcela evidentne neni mozne mluvit o „injection“…

    SL je proste a jednoduse neco docela jineho a popravde je to spise takovy kriplik…

    1. Jan Tichý

      Re: SL neni IoC ani DI!

      To není IMHO pravda. Když budu mluvit v rámci příkladu z článku, tak inverze obecně (ať už realizovaná pomocí DI, nebo pomocí SL) spočívá na principu, že o tom, která konkrétní implementace ICache se použije uvnitř FooService, se rozhoduje venku mimo FooService.

      A to je splněno jak u DI (kde si FooService řekne o některou implementaci ICache ve svém konstruktoru, setteru nebo property), tak u SL (kde si FooService lokátoru „dej mi nějakou implementaci ICache“).

      Akorát u DI to do ní zvenku i vstřiknu, proto „injection“. Zatímco u SL si to ta servisa vyzvedne sama. V obou případech ale určuji VENKU, mimo FooService, kterou konkrétní implementaci ICache ta FooService dostane. V obou případech se tedy jedná o princip IoC.

      Service Locator skutečně je z mnoha pohledů nevhodný a nečistý návrh. Nejvíc u něj asi vadí právě ta závislost všech ostatních tříd na něm, která vnáší do aplikace zbytečnou komplexitu (a vše s tím související, jako je omezená přenositelnost takových tříd jinam, složitější testovatelnost apod.).

      Na druhou stranu SL může mít oproti DI někdy i nezanedbatelné výhody. Zejména ve chvíli, kdy ho nevolám staticky, ale předávám si jeho instanci, tak můžu vesele využívat přepínání kontextu v různých částech aplikace. To je ale spíš okrajová záležitost…

      1. alefo

        Re: SL neni IoC ani DI!

        To je akademicka debata, ale Fowler to chape v [1] ako dve alternativy, kde exaktne hovori, ze Dependency Injection je vec, kde trieda dostane instancie zvonku bez toho, aby si ich musela sama pozhanat a Service Locator je … well Service Locator. Zaroven konstatuje, ze existuju aj hybridne pristupy (Apache Avalon, nech mu je zem lahka).

        [1] http://martinfowler.com/articles/injection.html

        1. Aleš Roubíček

          Re: SL neni IoC ani DI!

          Odkazovat Fowlera v tomto případě není zrovna šťastné, přeci jen jsou jeho články na toto téma poněkud zastaralé.

          Bohužel, seriál není o DI ale o IoC a pak jmenuje jednu z technik DI ServiceLocator, který je dnes považován za anti-pattern. Takže považuji za trochu nešťastné věnovat mu tolik místa a nezmínit, že jde o „zlo“.

          Dále je dobré zmínit, že property injection a setter injection jsou de facto to samé, protože property je je pouhou abstrakcí nad gettery/settery. V článku je bohužel pod pojmem property injection ukázána injekce do fieldů pomocí reflexe, což je stejný anti-pattern jako SL.

          Dále jsem v článku nenašel popis základního rozdílu mezi constructor injection a setter injection – a tou je ne/povinnost závislostí. Setter injection by se měla používat pro injektování nepovinných závislostí a nejlépe ve spojení se vzorem NullObject, který nám zajistí fungující objekt i bez provedené injektáže. V settru pak stačí pouhá kontrola, že není injektována reference na null.

          „Chuck Norris si závislosti nevytváří, on je deklaruje.“

          1. manik.cze

            Re: SL neni IoC ani DI!

            Seriál je orientovaný na DI, IoC a SL jsou zmíněny pouze jako související pojmy. SL je tu věnováno „tolik“ prostoru, protože myslím, že se to spoustě lidí plete (těch, co s DI zkušenosti nemají – pro ty je seriál koncipován). Více minimalisticky se mi to už popsat nepodařilo.

            Sám SL za anti-pattern také považuji, ale do článku se mi to spíš psát nechtělo. Koneckonců používá to spousta frameworků, takže se s tím lidé prostě dříve nebo později setkají.

            Ano je to to samé, ale vzhledem k tomu, že se setování přímo do public properties IMHO moc nepoužívá, tak jsem tomu nějak nevěnoval pozornost. Injekce pomocí reflexe je zde protože to je jedna z možností, s kterou pracují některé kontejnery a jako označují ji právě jako Property Injection (nebo se snad mýlím?).

            Co přesně myslíš nepovinnou závislostí? Můžeš mi to prosím vysvětlit ideálně na nějakém konkrétním příkladu?

            1. Jan Tichý

              Re: SL neni IoC ani DI!

              Nepovinná závislost – třeba logger. Když se injektuje, tak se loguje. Když se neinjektuje, tak se neloguje.

            2. Aleš Roubíček

              Re: SL neni IoC ani DI!

              Nepovinné závislosti jsou závisloti, bez kterých objekt může fungovat. Jedním z příkladů budiž logování (které by mělo být implementováno pomocí AOP, ale ne všude je dostupné. Nebo je možné použít logovací dekorátor…)

              Objekt bez logovací závislosti může vykonávat svou činnost, ale v případě, že je mu pomocí setter injection poskytnuta implemetace logeru, může navíc i logovat. NullLogger v tomto případě sice přijímá logovací zprávy, ale ve skutečnosti s nimi nic nedělá, jeho implementace je „prázdná.“

              1. manik.cze

                Re: SL neni IoC ani DI!

                No já to chápu tak, že buď mám nějaký objekt, který tu závislost vyžaduje (třeba ten Dekorátor) a můžu mu předat NullLogger, nebo tam ta závislost není a nepředávám nikde nic.

                To, že se jeden a ten samý objekt bude chovat jinak (vynechá/přidá) fukčnost podle toho, jestli jsem někde zavolal nějaký setter (nebo předal nepovinný parametr do konstruktoru) se mi moc nepozdává.

                1. Aleš Roubíček

                  Re: SL neni IoC ani DI!

                  V případě užití vzoru *NullObject* si takovou nepovinnou závislost uspokojí objekt sám. Podobně, jako jsou reference inicializovány null hodnotou. V C# by kód mohl vypadat následovně:

                  class Foo {
                    ILogger logger = NullLogger.Instance;
                  
                    public void Bar() {
                      logger.Log("something");
                    }
                  
                    public ILogger Logger {
                      set { logger = value ?? NullLogger.Instance; }
                    }
                  } 

                  V tomto případě nikdy nedojde k `NullReference­Exception` i když neobsahuje defenzivní kód.

                  1. manik.cze

                    Re: SL neni IoC ani DI!

                    Aha, no tam se mi nelíbí ta (zbytečná) další závislost na konkrétní třídě. To, jak tohle vidím já – závislost je vždy povinná, se potom zkombinuje s vhodnou konfigurací kontejneru – o nějaké implementaci řeknu, že je defaultní a ta se pak použije všude tam, kde není výslovně řečeno jinak. K `NullReference­Exception` taky nikdy dojít nemůže, rozhraní i chování je transparentní a nemusím kvůli tomu psát žádné speciální konstrukce do třídy.

                    1. Aleš Roubíček

                      Re: SL neni IoC ani DI!

                      ale tím vytvoříš z nepovinné závislosti povinnou. Rozdíl je v semantice.

                      Tady dávám veřejným rozhranním jasně najevo, jak je s objektem zamýšleno pracovat bez nutnosti to explicitně deklarovat na dalším místě (konfigurace kontejneru).

                      Ad závislost na konkrétní třídě. Tahle závislost je zcela bezpečná. Zaprvé její implementace nedělá vůbec nic (neobsahuje žádný globální stav ani nemá vedlejší efekty), je to jako mít závislost na `null`. Zadruhé se dá pomocí setter injection snadno nahradit jinou implementací `ILogger`.

                      1. manik.cze

                        Re: SL neni IoC ani DI!

                        Já ten rozdíl vidím a chápu. Nemám na to žádný vyhraněný názor, ale stejně si myslím, že by se třída od téhle „logiky“ okolo tohohle měla oprostit. Jí to má být úplně fuk, co jí kdo nastaví (a to samé má říkat svým rozhraním – setter imho znamená „můžeš mi to změnit kdykoli za běhu, mě je to jedno“). To, že děláš NullObject je pouze náhražka za to, že pak nemusíš psát všude dál v kódu if (logger != null), jinak je to to samé. V obou případech si ta třída uvnitř sebe defacto kontroluje zda k logování dojde nebo ne.

                        Když si vezmeš SRP, tak ten říká, že by v třídě nemělo být nic navíc, než co je nezbytně nutné. Pokud to nutné je, tak to používá a jde tedy o závislost, kterou by si měla nechat dodat. Pokud není, tak můžu použít např. zmiňovaný dekorátor.

                        NullObject je tedy podle mého právě vhodný použít čistě „zvenku“ – třída požaduje nějaké rozhraní a my víme, že nechceme aby dělala tu jednu část, kterou nepotřebujeme, takže to je spíš takový „hack“.

                        To jak se tam ten NullObject dostane pak už záleží na okolní aplikaci – pravděpodobně kontejner. A pak už záleží na tom jaké schopnosti konfigurace kontejner má – jak moc to bude transparentní nebo opruz psaní. Jak jsem psal – napříč celou aplikací prohlásím, že např. EchoLogger je můj defaultní (a ten se pak autowiringem doplňuje na ta místa, kde je potřeba daný interface). Pokud ale v některých (tady pozor – co znamená to některých – kde a kdo to bude rozlišovat jestli se to tam setne nebo ne?) případech budu chtít jiné chování, tak si řeknu, že tahle služba Foo se předá jinak nakonfigurovaná (s NullLoggerem) – ale to pak musím někde stejně explicitně napsat.

                        Další věc, která je způsobená tímhle je, že ten NullObject musí mít buď statický přístup k instanci (jako v tvojí ukázce) – tzn. pro mě by to např. znamenalo tenhle přístup ve všech těch NullObjectech vyrobit (+ Foo je na tomhle přístupu závislá). Alternativou je samozřejmě použít new, ale to by se vytvářely pořád dokola zbytečné objekty…Pokud konfiguruju zvenku můžu předávat pořád jednu a tu samou instanci, aniž bych k ní něco statického musel dopisovat.

                        Ještě otázka – doteď jsme mluvili o nepovinných závislostech pouze s výchozím NullObjectem. Používáš to pouze v téhle kombinaci nebo i pro určení defaultní implementace, která něco má opravu dělat (aby opět bylo naznačeno jak je s objektem zamýšleno pracovat)?

                        1. Aleš Roubíček

                          Re: SL neni IoC ani DI!

                          NullObject ale není defaultní implementace, je to neimplementace. :) Proto není nic špatného na tom, mít jí tam tímto způsobem zadrátovanou. Jak říkám, je to jako null, ale je silně typový a referenčně bezpečný.

                          To, že tam ty defenzivní ify nejsou, je právě tou velikou výhodou. V implementaci se zaměřuješ na opravdovou logiku a neřešít mezní případy, nezvyšuješ cyklomatickou komplexitu…

                          1. manik.cze

                            Re: SL neni IoC ani DI!

                            Ano, samozřejmě! O samotných NullObjectech a jejich funkci nepolemizuju. Zajímaly mě ty ostatní věci.

                            1. Aleš Roubíček

                              Re: SL neni IoC ani DI!

                              To z toho vychází. :) V ostatních případech to přípustné není, protože pak už pracuješ s implementací, která může mít vedlejší efekty a to není žádoucí.

                        2. Nox

                          Re: SL neni IoC ani DI!

                          To, že děláš NullObject je pouze náhražka za to, že pak nemusíš psát všude dál v kódu if (logger != null), jinak je to to samé.
                          Myslim že je lepší lehce zvýšit komplexitu implementace než náročnost použití… přidáš si jeded až pár ifů a už pak nikde nemusíš vytvářet dummy logger
                          To co píšeš zde ten setter skutečně znamená
                          (mj. pokud bude argument povinný, tak si třída přece uvnitř sebe NEkontroluje jestli logovat bude nebo ne… to by určil uživatel předáním dummy loggeru nebo skutečného, ale to je off topic)

                          A navíc souhlasím s tím, že jde prostě o jinou sémantiku, pokud je závislost v konstruktoru, třída říká že nutně potřebuje ke svému chodu logger. Pokud je setterem, říká jejímu uživateli, že je logger nepovinný … i když chápu že toto už je možná trochu subjektivní

                  2. Michal Augustýn

                    Re: SL neni IoC ani DI!

                    Možná by to bylo čitelnější/pocho­pitelnější, kdyby tam bylo něco takového:
                    ILogger logger = NullObject<ILog­ger>.Instance;

          2. martin

            Re: SL neni IoC ani DI!

            „property injection ukázána injekce do fieldů pomocí reflexe, což je stejný anti-pattern“
            zaujimali by ma dovody, preco je to antipattern – mozes o tom nieco viac napisat, pripadne dat odkaz na clanok/clanky?

            1. Aleš Roubíček

              Re: SL neni IoC ani DI!

              Protože to
              1. zjevně porušuje zapouzdření
              2. porušuje pricip Inversion of Control, objekt nedeklaruje své závislosti navenek, ale má přímou silnou závislost na infrastrukturním kódu (anotace). Jde o deklarativní service lokátor, kód rozhodně není lepší.

  2. Michal Kára

    Spring

    Pěkně to má vyřešení framework Spring – používá injection do anotovaných properties (které mohou být private). Člověk jen napíše

    @Autowired
    private Trida promenna;

    a je to :-)

    Constructor injection jsem taky používal, ale tam je u složitějších případů potřeba ručně vyřešit závislosti – které objekty vytvářet nejdřív, protože jsou potřeba jako parametry při vytváření dalších objektů.

    1. Jan Tichý

      Re: Spring

      „tam je u složitějších případů potřeba ručně vyřešit závislosti – které objekty vytvářet nejdřív, protože jsou potřeba jako parametry při vytváření dalších objektů.“

      To přece vůbec nesouvisí jenom s constructor injection. U úplně každého typu injektáže si člověk může závislosti řešit ručně, nebo to za sebe nechat dělat nějaký kontejner. A popravdě je to drbání pravou rukou za levým uchem, pokud využívám DI bez kontejneru, který za mě automaticky vytváří všechny třídy, sám řeší a hlídá všechny závislosti včetně správného pořadí apod. O DI kontejnerech bude tuším příští díl tohoto seriálu.

    2. alefo

      Re: Spring

      Mate nejake negativne skusenosti s rucnym riesenymi zavislostami v Constructor Injectione v Springu?

      Ved tam sa to vyriesi automaticky, ci nie?

        1. uživatel Springu

          Re: Spring

          Ve Springu je řešení závislostí automaticky. Výjimkou jsou situace kdy závislosti jsou „divné“, Spring je nemá šanci poznat, a vývojář je musí popsat extra – v XML jde o atribut depends-on. V každém DI containeru který má za něco stát to samozřejmě musí být automaticky, jinak se z toho vývojář zblázní. Prostě jednou už vývojář dal najevo, že objekt A má být předán jako parametr objektu B, takže z toho plyne jednak že objekt A se musí vytvořit (tranzitivně včetně všech svých závislostí) před objektem B, a na konci zase že objekt B se bude rušit dříve než objekt A.

          1. alefo

            Re: Spring

            Ja som to chápal tak, že construction injection v Springu je rovnaký ako setter injection, teda že okrem „divných“ prípadov netreba riešiť nič špeciálne (strom závislostí medzi beanmi si Spring vyrobí automaticky.)

            So setter injection som s tým nikdy nemal pri malých projektoch problém a keďže constructor injection nepoužívam, nie je mi jasné, čo tým myslí pán Kára.

    3. X

      Re: Spring

      Ještě lépe má DI pomocí anotací vyřešeno Google Guice. Ohledně toho constructor injection jste musel něco dělat špatně, tam řešení závislostí funguje naprosto automaticky, právě z toho důvodu že jde o konstruktor a tedy má container jasně dané, které objekty dělat dřív a které později.

      1. Michal Kára

        Re: Spring

        Sorry, trochu jsem pomíchal dvě věci dohromady. Pokud na tím mám framework na řešení dependencies, tak to samozřejmě já řešit nemusím.

  3. Gaudentius

    a co type hinting?

    Jakým zpsůobem naložit se závislostmi, poku chci využít „type hinting“?

    classA
    {

    public function __construct(Za­pisovac $zaposivac)
    {

    }

    }

    new classA(new Zapisovac());

      1. Gaudentius

        Re: a co type hinting?

        No pokud jsem z článku pochopil správně, tak principem DI je zbavit třídu závislostí na jiných třídách?!

        Pokud tedy v třídě A chci instancovat třídu B, předám v třídě A třídě B konstruktorem závislosti/instance jiných tříd, které potřebuji aby třída B měla k dispozici.

        Pokud ale chci v třídě B použít „type hinting“, tak defakto již do třídy B závislost přenáším, protože v „type hinting“ definuji o proti čemu se má kontrolovat:

        class A
        {
        public function neco() { $a = new B( new C(); ) }
        }

        class B
        {
        public function __construct(C $c) { $thtis->c = $c }
        }

        V tride A instancuji tridu B a do tridy B pomoci „construct injection“ predavam instanci tridy C.

        Snad jsem to vysvětlil a nebo pokud v rámci DI není „tyho hinting“ problém, tak to motám?

        1. Ondřej Mirtes

          Re: a co type hinting?

          V DI zbavuješ obracíš vztah k závislostem (inversion of control) – namísto, aby sis je vytvářel či sháněl sám (operátor new či sahání do nějakého globálního registru), necháš si je do objektu injektnout zvenčí.

          Namísto konkrétní implementace si můžeš vyžádat interface nebo abstraktní třídu, ale to je detail. Jde hlavně o to, že neřešíš, jak nakonfigurované objekty ti přicházejí, to řeší ten, kdo ten objekt instanciuje.

          Kompozice objektů je jeden ze základních pilířů OOP a toho se samozřejmě zbavovat nechceš :)

Napsat komentář

Tato diskuse je již příliš stará, pravděpodobně již vám nikdo neodpoví. Pokud se chcete na něco zeptat, použijte diskusní server Devel.cz

Zdroj: https://www.zdrojak.cz/?p=3508