Přijde mi, že vaše definice a představa DI je nějaká divná, a možná to je důvod veliké diskuze pod minulým článkem.
Dependency Injection znamená: Pokud třída potřebuje volat něco na jiné třídě/interfacu, musí si ho nechat předat v konstruktoru/parametru metody, místo toho aby si instanci vyráběla sama. Nic víc, nic míň. Nijak nesouvisí s tím, kolik má ta dependence implementací, jestli je to interface nebo třída, ani ničím jiným.
Díky tomu je na první pohled patrné, jaké dependence třída má. Nejsou v těle jejích metod schované žádně Něco::getInstance() nebo jiná statická volání, která pokoutně získávají další dependence. Toto se velice hodí například v unit testech, ale pokud si dobře pamatuju jeden z prvních dílů seriálu, tak ty taky neuznáváte?
Například když z třídy vyřezávám část funkcionality do nové třídy v rámci refaktoringu, tak novou metodu neudělám static, i když bych třeba z pohodlnosti mohl. Rovnou novou dependenci "přiznám" a až ta nová třída sama začne potřebovat svoje dependence, nebudu muset přepisovat všechna místa, kde jsem volal NováTřída::nováMetoda, ale jen jedno místo, kde se vytváří instance té nové i původní třídy. Což může být konfigurace nějakého DI kontejneru, ale taky naprosto obyčejný PHP kód plný new - kód tzv. "dependency wiringu". Tohle místo může vypadat poměrně ošklivě a je to daň za použití DI, ale je to daň jediná a velice se to v dlouhodobém horizontu vyplatí. Je samozřejmě možné použít různé DI kontejnery, ale z mé zkušenosti to často ani není moc potřeba.
Pochopit mozem len to co napises, ak to myslis inak ako si to napisal, tak porucha nebude v primaci.
Pokud třída potřebuje volat něco na jiné třídě/interfacu, musí si ho nechat předat v konstruktoru/parametru metody, místo toho aby si instanci vyráběla sama.
Ako sa lisia konstruktory predka a potomka, kde potomok koli rozsireniu funkcnosti potrebuje dalsiu zavislost, o ktorej predok logicky nic netusi?
Zajímavý, že se tyhle "změny kontextu" a "porucha není v přijímači" dějou jenom tobě, zato hodně často. Ale určitě za to můžou všichni ostatní.
Jestli to správně chápu, tak tvrdíš, že konstruktor potomka musí mít stejnou signaturu jako konstruktor předka. Pokud je to pravda, tak jsi nepochopil LSP. Ten mluví o objektech, ne o třídách. Tzn. vytváření instance (konstruktor) opravdu LSP splňovat nemusí.
Nemáš pravdu, viz třeba tady: https://stackoverflow.com/questions/5490824/should-constructors-comply-with-the-liskov-substitution-principle
Jak s tím souvisí dynamicita jazyka nechápu už vůbec. Ale od tebe už začínám být zvyklej, že tvoje reakce jsou jak z generátoru náhodnýho textu.
Tak prosím, najdi mi důvěryhdonej zdroj, kterej bude tvrdit, že konstruktor musí splňovat LSP (hlavně ať to není yegor256, ten chápe OOP a SOLID podobně zvrhle jako ty).
Budeš se možná divit, ale já naopak PHP skoro nepoužívám (mohlo ti to dojít z toho, jak na něj v podstatě všude nadávám). Teď zrovna mě živí Java.
Neznám jazyk, kde by konstruktor musel mít stejnou signaturu jako předek. (C++ má určité povinné konstruktory ale ve výsledku i zde mě tvrzení platí.) Konstruktor prakticky nikdy není součástí rozhraní. Prakticky nikdy by to nedávalo smysl. Bavím se o dynamických i staticky kompilovaných jazycích. Principy OOP se v žádném případě neopírají o konstruktory. Váš popis OOP nerozeznávám v žádné variantě objektového programování teoretického, praktického nebo nějak všelijak deformovaného stylu. Můžete dát odkaz na objektový styl ze kterého čerpáte?
Lenze konstruktor v PHP je sucastou rozhrania, skuste si bud reflection alebo zavolat konstruktor na instancii objektu. V PHP je konstruktor normalna metoda (polymorfna ako vsetky ostatne), volana PO inicializacii instancie objektu. Tak oni normalne jazyky s nezmrsenym OOP ani nemaju konstruktor (napr ADA). Maximalne tak metodu ktora sa vola default po vytvoreni instancie (_constructor v PHP, __init__ v Pythone).
> Lenze konstruktor v PHP je sucastou rozhrania, skuste si bud reflection alebo zavolat konstruktor na instancii objektu. V PHP je konstruktor normalna metoda (polymorfna ako vsetky ostatne), volana PO inicializacii instancie objektu.
Ano, zkusil jsem si to a je to bohužel opravdu tak - PHP mě klidně nechá ručně zavolat __construct na už existující instanci, čímž ji v podstatě zničí. Taky mě například nechá zavolat statickou metodu na instanci. Pokud z tohohle chování odvozuješ obecně platný principy LSP, tak asi nemá smysl s tebou dál diskutovat. Jak už jsem ti doporučoval jinde - zkus někdy i jinej jazyk než PHP, podle jeho bugů fakt nemá smysl chápat svět. SOLID a jeho části fungujou stejně ve všech jazycích, protože jsou to jen principy, kterým je třeba rozumět - ty ses je sice dokázal naučit a teď je prosazuješ s nadšením náboženskýho fanatika, ale bohužel jsi je vůbec nepochopil.
Jen se zkus zamyslet - ty tvrdíš nějakou věc, všichni ostatní tvrdí opak. Buď jsou úplně blbí všichni ostatní, nebo jsi úplně blbej ty - co je pravděpodobnější (odpověď závisí na úrovni sebevědomí a ignorance dotazovaného)?
Lenze to opakovane volanie ju neznici, ale reinicializuje. A ak odovzdavas zavislosti cez konstruktor, tak sa dostanes aj k vlastnostiam objektu ktore by mali ostat private. V spojeni s tym ze si cachujes konfig, podla ineho vlakna, tak mas prava zapisu na spustitelny kod, ktory ti volanim konstruktoru na uz existujucich triedach moze narobit vcelku slusnu paseku. Byt tebou, tak sa poohliadnem po velmi dobrej poistke pre pripad sposobenych skod.
Kolko ludi tvrdi opak? Tak 90%? Holt to koreluje s odhadom ze len 10% vie skutocne programovat, ostatok kod lepi, napr. pomocou stackoverflow a ma debilne navyky, nad ktorymi sa nedokazu kriticky zamysliet, nie to sa ich zbavit.
> Lenze to opakovane volanie ju neznici, ale reinicializuje.
Nic jako "reinicializace" objektu neexistuje (už to slovo samo o sobě je patvar). Kromě jazyků typu C++, který chápou objekt v první řadě jako místo v paměti a ne jako myšlenkovou entitu.
Jen abysme si to teda shrnuli: Ty tvrdíš, že konstruktor musí splňovat LSP, takže konstruktor potomka musí mít stejnou signaturu jako konstruktor předka, ale potomek může mít víc závislotí, takže jediný správný konstruktor je ten bez parametrů? A všechny závislosti se musí předávat jinak? Setterem?
> A ak odovzdavas zavislosti cez konstruktor, tak sa dostanes aj k vlastnostiam objektu ktore by mali ostat private.
Samozřejmě jsou private. Ale ten objekt je nevlastní, takže nijak nevadí, že tu stejnou referenci má i někdo jiný. Jak je předáváš ty?
> V spojeni s tym ze si cachujes konfig, podla ineho vlakna, tak mas prava zapisu na spustitelny kod, ktory ti volanim konstruktoru na uz existujucich triedach moze narobit vcelku slusnu paseku.
Jak mi zavolání new Třída() způsobí paseku jinde? K existujícím instancím to přece přístup nemá. A tím "máš práva zápisu na spustitelný kód" myslíš koho? Nějakýho hypotetickýho útočníka, co mi místo configu vygeneruje new Třída()? Jak to udělá? A proč by rovnou neudělal něco lepšího jako třeba remote shell?
> Byt tebou, tak sa poohliadnem po velmi dobrej poistke pre pripad sposobenych skod.
LOL.
> Kolko ludi tvrdi opak? Tak 90%? Holt to koreluje s odhadom ze len 10% vie skutocne programovat, ostatok kod lepi, napr. pomocou stackoverflow a ma debilne navyky, nad ktorymi sa nedokazu kriticky zamysliet, nie to sa ich zbavit.
S tímhle se dá výjimečně souhlasit. Ale asi ne tak, jak by sis myslel - protože ty jsi naprosto jasně v těch 90 %. V podstatě všechno, čím tady tapetuješ diskuze, je špatně nebo alepsoň nepřesně, a závěry z toho vyvozuješ naprosto scestný.
Máš třeba svůj github nebo tak něco? Tvůj kód bych si občas rád prohlídnul kdybych měl pocit, že už mi dlouho nekrvácely oči. (Můj sice zatím není moc aktivné, ale prosím: https://github.com/kostislav - to, co dělám v práci, bohužel opensource není)
No ja na opensource veru cas nemam. Takze Kostislav, nazrel som akurat do toho PHP repa. Takyto kod by cez code review vo firme s pricetnymi kolegami nepresiel. Nehovorim ani tak o komentaroch, i ked je lepsie vediet z komentaru co ta metoda robi, ako lustit spagetak. Ale chapem, popisat spagetak v metode do komentaru je niekedy nadludsky vykon. To ze jedna metoda by mala zastresovat jednu funkcionalitu by mal respektovat aj junior. Ktomu prechadzaniu pola cez foreach, mozno dobry napad v jave, php si ale v tomto pripade nad tym polom vytvara iterator. Preto je foreach dobry pre metody s yield, Reflection::getMethods vracia pole, tam by som pouzil array_walk. Toto ale nevedia zvedsa ani mediori, preto leju foreach vsade.
K odovzdavaniu zavislosti cez konstruktor. Preco by mali byt zavislosti posielane objektu cez konstruktor. Aby sme sa tomu vyhli (parameter passing) tak mame prave dependency injection. Naviac pri tomto postupe je nutne aby vsetky zavislosti boli este pred zavolanim konstruktoru v pamati inicializovane. Aj tie ktore nebudu pouzite. To ma negativny dopad nielen na vykon ako taky, ale aj na odozvu. Vacsina ludi opusti web ak nedostane odozvu do cca 10 sec. Google stranky s dlhou odozvou penalizuje este prisnejsie. To ze web odrati pomalostou urcite percento ludi vedenie ako tak prekusne, ale prepad vo vysledkoch vyhladavania ich rozladi zarucene.
Ako ich predavam ja? Nemusim mat globalny singleton, trieda ktora splna interface Configurable, ma konstruktor s jedinym parametrom - $configContext, ktory obsahuje parametre zavislosti a na poziadanie ich instancuje v pamati. Ked si to prezenies cez profiler (ak ti teda cachegrind nerobi problem) tak zistis ze je tam dost drasticky rozdiel.
> No ja na opensource veru cas nemam.
To je nakonec vlastně dobře :-) Takže když tě shrnu: "Kód na ukázání nemám, zdroje podporující moje nesmyslný tvrzení taky ne, ale rozumím všemu líp než všichni ostatní a já jedinej mám pravdu". Tak určitě :-D
> To ze jedna metoda by mala zastresovat jednu funkcionalitu by mal respektovat aj junior.
Máš na mysli nějakou konkrétní, která tě uráží? Některý by se asi daly rozdělit, ale nic úplně hroznýho bych tam nečekal...
> Ktomu prechadzaniu pola cez foreach, mozno dobry napad v jave, php si ale v tomto pripade nad tym polom vytvara iterator.
No a? Jako že se alokuje o jeden objekt víc? To nemá absoultně smysl řešit. Důležitá je čitelnost. Pokud chceš takhle pitvat performance na úkor všeho ostatního, použij vhodnější jazyk. Btw v Javě se ten iterátor vytváří taky.
> tam by som pouzil array_walk.
Kterej místo iterátoru zase musí vytvořit instanci té anonymní funkce. To sis vážně pomohl.
> Preco by mali byt zavislosti posielane objektu cez konstruktor. Aby sme sa tomu vyhli (parameter passing) tak mame prave dependency injection.
Možná by sis měl přečíst aspoň definici DI.
> $configContext, ktory obsahuje parametre zavislosti
Takže místo DI používáš Service Locator a ani o tom nevíš. To jsi mohl říct rovnou a mohli jsme si celou tuhle diskuzi ušetřit....
Tady musím reagovat. Konstruktor potomka nemusí být stejný, problém je, když musím změnit konstruktor předka, protože přibyla další dependency. Pak musím taky změnit všechny volání konstruktoru předka ve všech konstruktorech potomků a typicky přidat dependency do konstruktorů potomků.
Tohle peklo vidím na stávajícím projektu u datových objektů. Řešení je použít builder pattern u datových objektů, problém je, že třeba Lombok u Builder stále upravuje konstruktor místo, aby si vzal atributy přímo z Builder. U DI komponent je to horší, Builder snad DI implementace ani nepodporují. Dá se použít DI setter, který bude ale fungovat jenom do doby, než potomek tu metodu přepíše a nebude volat super, bo v době implementace metoda v předku ještě neexistovala. Dá se i předat DI factory samotná, ale to už trochu porušuje princip IoC.
Takže tady nějaké problémy jsou. Nenastávají příliš často, ale řešení příliš dobrá nejsou.
Tohle je pravda.
On Lombok obecně s dědičností moc dobře nefunguje (i když mám dojem, že by mohl, kdyby autoři tak nelpěli na podpoře Eclipse compileru).
Navíc teda použití Builderu má oproti konstruktoru tu nevýhodu, že zapomenutá dependence selže až za běhu místo hned při kompilaci.
Je možné, že třeba Spring by se dal ohnout, aby podporoval Builder (jistý si ale nejsem, v některých ohledech se dá ohnout dost, ale v jiných hodně málo), ale out of the box to zdá se opravdu neumí.
Ve své odpovědi jsem tohle neuvedl, protože mi to vůbec nedošlo - v našem kódu totiž děděičnost nepoužíváme, tak jsem na tenhle problém nikdy nenarazil.
Ad kod plny new: Toto je maximalne blby napad, kazde new alokuje pamat. V pripade vyvoja, ked ku aplikacii pristupuje jeden clovek, tak je to nepostrehnutelne. Pri nasadenina server kde k aplikacii pristupuje tisice uzivatelov je alokacia extremne draha zalezitost, vzhladom k tomu ako funguje chranena pamat. A to i v pripade kontajnerov, alevo virtualu s dynamicky alokovanou pamatou.
Přesně tak. Je vlastně s podivem, že princip DI není více zakomponován např. do objektově orientovaných programovacích jazyků. Že se stále drží toho operátoru new, který není ničím jiným, než maskovaným: „Potřebujeme něco udělat. Dobře, takže musíme začít tím, že si alokujeme potřebnou paměť.“
Pokud má objekt dělat jenom jednu věc, nezbývá mu nic jiného, než závislosti, které potřebuje, dostat nějak zvenku, třeba v konstruktoru. Jakmile si své závislosti sám vytváří, dělá ten objekt dvě věci – vytváří si závislosti a vedle toho dělá i to, pro co byl původně určen.
Přečteno 21 846×
Přečteno 19 817×
Přečteno 18 835×
Přečteno 18 549×
Přečteno 17 429×