Dependency Injection Container: TODO
- Filip Procházka
- Moderator | 4668
Nechtěl jsem zakládat flame, ale protože je to v diskuzi a né feature request…
V presenterech je potřeba být lazy co nejvíc, na tom se určitě shodneme. Implementace je tedy k diskuzi, jedna trošku prasácká cesta už je a zdá se být docela oblíbená. Další zajímavé se vynořují.
Když ale začnou lidé cpát tyhle zbytečnosti do modelu, začne vznikat cosi odporného a na každém druhém řádku bude něco ve stylu
$this->db->get()->query(...);
protože co kdyby se databáze nepoužila…
Navíc tím budeš nepřímo navádět lidi, aby vytvářeli god objekty, protože proč ne – celé je to lazy :) Ospravedlňuješ tím prasení.
Teď accessory nejsou a všichni se snažíme vést nettisty k psaní malých tříd s minimem závislostí a odpovědností. Když něco chceš v třídě, předej si to přes konstruktor, má třída hodně závislostí? Asi toho dělá víc než by měla.
Další problém je, že když bude v Nette @inject
by default,
tak se tahle metrika vypaří, protože napsat 10 závislostí pod sebe
schovaných v třídě pere do očí daleko méně, než když si je musím
explicitně předat přes konstruktor.
No a když už budeme mít inject na veřejné proměnné, proč ho rovnou nepovolit i na protected a private :) A to se blbě vysvětluje…
Prosím Davide, neustupuj – ty jsi strážce kvality proslulého Nette Frameworku a tohle není dobrá cesta :)
Mám nápad – vymyslíme jak to implementovat jako
rozšíření a upravíme Nette\DI
(a
Nette\Application
) tak, aby tyhle rozšíření hackovaly Nette co
nejméně. Budeme mít rozšířitelnější Nette\DI
a nebudeme by
default navádět k tomuhle java-like prznění
objektů :)
WIN – WIN
Ještě doplním jednoduché měření, co prováděl Majkl578 – mluví ve prospěch malých objektů s primitivním konstruktorem. Accessory jsou zbytečné. Vždy to jde řešit lépe a bez nich.
Editoval Filip Procházka (12. 3. 2013 20:09)
- David Grudl
- Nette Core | 8227
Filip Procházka napsal(a):
Když ale začnou lidé cpát tyhle zbytečnosti do modelu, začne vznikat cosi odporného a na každém druhém řádku bude něco ve stylu
$this->db->get()->query(...);
protože co kdyby se databáze nepoužila…
Toho se rozhodně neobávám. Vždy je jednodušší napsat
/** @inject @var Nette\Database\Connection */
a pak přímo
pracovat s databází, než vytvářet interface accessoru a pak všude
používat $this->db->get()
.
Navíc tím budeš nepřímo navádět lidi, aby vytvářeli god objekty, protože proč ne – celé je to lazy :) Ospravedlňuješ tím prasení.
Nechápu… Co je lazy? A jak souvisí lazy s god objekty?
Teď accessory nejsou a všichni se snažíme vést nettisty k psaní malých tříd s minimem závislostí a odpovědností. Když něco chceš v třídě, předej si to přes konstruktor, má třída hodně závislostí? Asi toho dělá víc než by měla.
Na tomhle anotace @inject, accessory ani továrny absolutně nic nemění. Opět jde jen o objekt, který se předává.
Další problém je, že když bude v Nette
@inject
by default, tak se tahle metrika vypaří, protože napsat 10 závislostí pod sebe schovaných v třídě pere do očí daleko méně, než když si je musím explicitně předat přes konstruktor.
Ty, které tahle metrika nezajímá, si stejně žádné závislosti nepředávají a používají statiku. Ty, které zajímá, nepotřebují, aby je to pralo do očí. A psát zbytečný/rutinní kód navíc otravuje oba tábory.
No a když už budeme mít inject na veřejné proměnné, proč ho rovnou nepovolit i na protected a private :) A to se blbě vysvětluje…
To je stejný případ jako protected metoda inject().
Prosím Davide, neustupuj – ty jsi strážce kvality proslulého Nette Frameworku a tohle není dobrá cesta :)
Jsem přesvědčen, že je ;)
- Šaman
- Člen | 2662
@Filip Procházka:
V psaní ošklivého kódu mi Nette nemůže zabránit, když jsem lama a
trochu se snažím. Koneckonců ani nemusím používat model a jet na VC
architekturu.
A injecty v modelu používám už teď, jen je musím plnit v configu. Jsou to prostě jinak pojmenované settery. Mnohdy potřebuji předávat modely do servisní vrstvy lazy – nevím se kterým repozitářem budu pracovat, protože servisní vrstva mi obaluje velkou část modelu. A že to vypadá, že ta moje servisní vrstva dělá hodně věcí? Jen to tak vypadá – většinou deleguje požadavek na některý repository, ale třeba pokud chci třeba založit novou s novým autorem, už se bez ní neobejdu. (Pokud bych to nenechával na servisní vrstvě, tak mi bude muset autora založit BookRepository a opět potřebuji lazy přístup k AuthorRepository. Přece nebudu vytvářet AuthorRepository (do konstruktoru), pokud si budu chtít jen přečíst seznam knih v kategorii..)
- Honza Marek
- Člen | 1664
Šaman napsal(a):
Přece nebudu vytvářet AuthorRepository (do konstruktoru), pokud si budu chtít jen přečíst seznam knih v kategorii..)
Nechápu, kde se bere to přesvědčení, že vytvoření nějakého lazy bazmeku (což je obyčejný objekt) je levnější než vytvoření běžné service (což je obyčejný objekt).
- Filip Procházka
- Moderator | 4668
David Grudl napsal(a):
Navíc tím budeš nepřímo navádět lidi, aby vytvářeli god objekty, protože proč ne – celé je to lazy :) Ospravedlňuješ tím prasení.
Nechápu… Co je lazy? A jak souvisí lazy s god objekty?
Myslel jsem tím accessory. Vsadím se o oběd v indické, že do roka a do dne, od momentu kdy bude ve stable Nette podpora accessorů, se nám tady na fóru objeví člověk, který bude mít na naprostou většinu služeb accessory a bude je používat na vše :) A ani se za to nebude stydět.
Jsem skálopevně přesvědčen, že generované accessory jsou zbytečné a pouze uživateli ospravedlňují god objekty.
Na tomhle anotace @inject, accessory ani továrny absolutně nic nemění. Opět jde jen o objekt, který se předává.
Tak jak jsi to implementoval teď, tak je to ok – protože je to čisté DI, předáváš do public proměnné.
Ovšem stačilo pár hodin a moje slova se plní
No a když už budeme mít inject na veřejné proměnné, proč ho rovnou nepovolit i na protected a private :) A to se blbě vysvětluje…
Honza Marek napsal(a):
Nechápu, kde se bere to přesvědčení, že vytvoření nějakého lazy bazmeku (což je obyčejný objekt) je levnější než vytvoření běžné service (což je obyčejný objekt).
+1
Editoval Filip Procházka (13. 3. 2013 10:02)
- Vojtěch Dobeš
- Gold Partner | 1316
Ach ty pojmy…
Šaman napsal(a):
- předávat modely do servisní vrstvy
- servisní vrstva mi obaluje velkou část modelu
Cožpak servisní vrstva není součástí modelu (toho pod M v MVC)? Všechno jsou to jen třídy nějak implementující business logiku aplikace, ne? Ano, dá občas práci navrhnout to dobře, ale Nette umožňuje vytvářet krásnou architekturu s tak velkou dávkou pomoci, že snad není rozumné výmluvy :). Proč prostě neudělat hezkou třídu na vytvoření knihy s novým autorem, a předat jí co potřebuje? Svázání jakýmsi rozdělením tříd do systému neslučitelných kast z toho úplně čiší.
- David Grudl
- Nette Core | 8227
Honza Marek napsal(a):
Nechápu, kde se bere to přesvědčení, že vytvoření nějakého lazy bazmeku (což je obyčejný objekt) je levnější než vytvoření běžné service (což je obyčejný objekt).
A skutečně: new stdClass (obyčejný objekt) je levnější, než new PDO (obyčejný objekt).
Pochopitelně člověk se musí řídit rozumem, jinak režie lazy obálek výkon sníží.
- Tharos
- Člen | 1030
Ale PDO
, které se okamžitě připojuje k databázi, není
vůbec jen tak nějaký obyčejný objekt :).
Nepřipadá vám (zastáncům „accessorů“), že těch objektů, pro které je vytvoření nějaké proxy třídy skutečně přínosné, je v typické aplikaci úplné minimum? Pokud ano (a já si to myslím), tak těch pár jednotek pomocných tříd přece nemá smysl generovat. To si každý napíše snadno sám ručně.
Skoro bych se vsadil, že zavedení automatického generování něčeho podobného povede k nadužívání a přinese to pouze negativní dopady (pomalejší a méně přehledné aplikace).
- David Grudl
- Nette Core | 8227
Tharos napsal(a):
Nepřipadá vám (zastáncům „accessorů“), že těch objektů, pro které je vytvoření nějaké proxy třídy skutečně přínosné, je v typické aplikaci úplné minimum?
Pochopitelně. Tvrdí někdo opak?
Pokud ano (a já si to myslím), tak těch pár jednotek pomocných tříd přece nemá smysl generovat. To si každý napíše snadno sám ručně.
A pravděpodobně blbě, tj. napíše pomocnou třídu, ale už nenapíše interface, jehož je tato třída implementací. Bez interface vytvoří závislost na DI kontaineru.
Pokud mi napíše interface, rád mu tu implementaci vygeneruju: řešení pak bude čisté.
Skoro bych se vsadil, že zavedení automatického generování něčeho podobného povede k nadužívání a přinese to pouze negativní dopady (pomalejší a méně přehledné aplikace).
Jasně, už vidím, jak všichni píšou desítky rozhraní accessorů pro každou službu a poté k nim o něco složitěji přistupují.
- Filip Procházka
- Moderator | 4668
David Grudl napsal(a):
Jasně, už vidím, jak všichni píšou desítky rozhraní accessorů pro každou službu a poté k nim o něco složitěji přistupují.
No hele, upřímně … tebe by to překvapilo?
Můžu ti upřímně říct, že vím minimálně o jednom projektu, který to už teď dělá na základě tvého článku – píšou si accessory na opravdu hodně věcí. Tušíš co by to s takovým projektem udělalo, kdyby jim to Nette zvládlo generovat? Nechci ani domýšlet, takhle brzo po obědě :)
A proč by to vlastně někdo dělal? Protože má starou aplikaci, která je zbastlená a pomalá. V Nette se objeví feature, že služby se budou vytvářet lazy… Kdysi někde četl, že se přece používá teď to DI, tak si předává třídy, ale má jich tam moc a jsou zbastlené, tak co s tím? Co udělá v zoufalosti takovej člověk jako první, protože na něj tlačí vedení, že appka je pomalá? Na všechno vytvoří accessor, protože četl, že to je taky to DI.
Přinese to víc problémů než užitku :)
Editoval Filip Procházka (13. 3. 2013 13:43)
- vvoody
- Člen | 910
Filip, prečo chceš aby každá cvičená opica, ktorá dostane do ruky nette, naprogramovala ukážkovú aplikáciu? Nemyslím si že je možné naprogramovať framework tak, aby toto dokázal. Zrušíme celý DIC len preto, lebo ho časť programátorov používa ako service locator? (blbý príklad? :D)
- Tharos
- Člen | 1030
Pro těch pár tříd, jejichž instance se vytvářejí draze…
Mně by se skoro víc líbilo, kdyby Nette umělo pro takové třídy vygenerovat regulérní proxy třídu. Fungovalo by to zhruba následovně:
- V konfiguraci u definice služby jen navíc uvedu, že k ní chci vytvořit proxy třídu a tu chci i autowirovat.
- Nette ověří, že k dané službě existuje interface. Pokud ne, kompilace skončí chybou. Psaní (a údržba) takového interface je pruda, ale v tomto případě by to mohl být vhodný filtr pro to, aby nebylo této funcionality nadužíváno. Zkrátka: chceš-li opravdu s touhle službou pracovat lazy způsobem, nadefinuj pro ni interface, jinak máš smůlu.
- Nette na základě toho interface vygeneruje proxy třídu s lazy vytvořením instance zastupované třídy až v momentě, kdy je to zapotřebí.
- V presenterech a všude jinde se taková služba bude autowirovat přes
interface. Zkrátka uvedu, že nechci
Service\Articles
aleService\IArticles
. A kontejner mi u takových služeb předá proxy. - V presenteru (a všude jinde) pak s danou službou budu pracovat úplně normálním způsobem (ani nebudu vědět, že pracuji s proxy implementací).
Co si myslíte o tomhle?
Edit: Teď jsem si teprve pořádně všiml tohohle:
David Grudl napsal(a):
Pokud mi napíše interface, rád mu tu implementaci vygeneruju: řešení pak bude čisté.
No, když existuje poctivě napsané interface, tak je skoro zbytečné
generovat nějaký accessor, ale je lepší vygenerovat rovnou proxy, nebo ne?
Rozhodně to zjednoduší používání
($this->service->doSomething()
namísto
$this->serviceAccessor->get()->doSomething()
).
Editoval Tharos (13. 3. 2013 14:51)
- hrach
- Člen | 1838
Nemůžu se než podivovat Hosiplanovu přístupu. Pořád bojuje za nějakou teoretickou čistotu v Nette, ale v praxi všichni víme, že je nepoužitelná. Prasit se bude vždy, Hosiplan to dělá, jen na to nemá koule to hodit do Kdyby. (hosiplan/nette-autowire-properties)
- Je naprosto legitimní vyžadovat lazy přístup:
- Automatické proxy mi přijde jako totální blbost a magie.
- Accessory mi přijdou jako dobré řešení
- jejich hnusná syntaxe (
->get()
) mě dostatečně odradí, abych je nepoužíval všude - v ostatní případech si nechám injectnout rovnou objekt
- jejich hnusná syntaxe (
- Proč je nutné vyžadovat lazy přístup:
- cyklická závislost:
- mějme dva objekty, které navzájem se občas využívají, typicky mějme dvě fasády. Třeba UsersFacade a ArticlesFacade. Dokážu si vcelku jednoduše představit jejich cyklickou závislost. A pokud se vám to nějak nezdá, představte si aplikaci, která ma 30 fasád, a z fleku mi tu odpřisáhněte, že nikdy nemůže nastat cyklická závislost. Jak by se korektně měli injektovat služby v DICu, např. přes constructor, když nebudou lazy?
- dominová závislost:
- mějme opět fasády: jak správně už padlo, vytvoření jednoho objektu nic nestojí. Ale mám v aplikaci 30 fasád, kdy většina zavisí na jedné a více fasádách. Tady už vůbec není zanedbatelné vytvoření všech 30 tříd. Navíc, z principu DI já ani netuším, jak daleko mi domino dojede. Fasáda může být vyžadovat cache. A tato cache může při vytvoření něco invalidovat, připojovat se k memcache serveru, … Čím víc budu mít specializovaných malých tříd, tím více bude dominový efekt zřetelnější a méně předvídatelný. Prosím uvědomte si to.
- cyklická závislost:
Shrnutí: Aktuální stav JE a po implementování tohoto i BUDE
horší méně pohodlný, než se statickým peklem. Když už jsme
si celé aplikace přepsali to ->context->session
a nic tím
navíc nezískali, rád bych, aby cíl byl vysoký a nebyli spokojeni
s aktuálním stavem injectSession()
, ale byli ještě
pohodlnější a přitom čistí. Občas se musí udělat kompromis.
@inject
na public property je naprosto čistý. Automatický
accessor je naprosto čistý. To, že se něco může zneužít není důvod
proti tomu samotnému. Já se ptám, jak udělat accessory pohodlnějšími? Co
třeba přidat __invoke()
? (Bohužel myslím nebude fungovat na
property. $this->pdo()->...
)
- hrach
- Člen | 1838
Tharos napsal(a):
David Grudl napsal(a):
Pokud mi napíše interface, rád mu tu implementaci vygeneruju: řešení pak bude čisté.No, když existuje poctivě napsané interface, tak je skoro zbytečné generovat nějaký accessor, ale je lepší vygenerovat rovnou proxy, nebo ne? Rozhodně to zjednoduší používání (
$this->service->doSomething()
namísto$this->serviceAccessor->get()->doSomething()
).
No je trochu rozdil psat interface na getter a celou třídu.
- Tharos
- Člen | 1030
hrach napsal(a):
- Automatické proxy mi přijde jako totální blbost a magie.
Co je na proxy magického? IMHO je to naprosto čistá aplikace programování proti rozhraní.
Mohl bys to prosím více rozvést? Nemyslím to vůbec ve zlém, fakt by mě zajímalo, co přesně Tě k takovému závěru vede.
- Honza Marek
- Člen | 1664
David Grudl napsal(a):
A skutečně: new stdClass (obyčejný objekt) je levnější, než new PDO (obyčejný objekt).
new PDO není obyčejný, ale neobyčejně zprasený objekt. Naštěstí ani PDO nepotřebuje lazy přístup, protože je beztak vytvořeno při každém requestu, protože všechny stránky webu potřebují databázi.
Pokud mě chceš o něčem přesvědčit, chce to lepší příklad ;)
- redhead
- Člen | 1313
Souhlasím s @Tharosem. Generování proxy proti rozhraní mi přijde daleko lepší než mít accessory. Když interface nebude, vyhodit výjimku. Naprosto dokonalé a neprůstřelné řešení, které navíc bude vést lidi (začátečníky) k programování proti rozhraní.
@inject nad public property OK. Nejspíš to používat nebudu, protože i já bych se z lenosti dokopal k prasení. Ale prosím accessory ne-e!
- David Grudl
- Nette Core | 8227
- accessor je podmnožina generovaných factory, jen přidává omezení, že vždy vrací tentýž objekt. Ve smyslu výrokové logiky, jakákoliv námitka proti accessoru je námitkou proti generovaným factory. Takže prosím, přestaňte používat termín accessor a všechny námitky adresujte přímo proti factories. Třeba to rozjasní diskusi.
- proxy je legitimní pattern, ale využívá se spíš při testování, takže DI schopnost je generovat nemá. Bohužel PHP neumí implicitní rozhraní a v případě např. PDO bychom si stejně nepomohli.
- Filip Procházka
- Moderator | 4668
hrach napsal(a):
Nemůžu se než podivovat Hosiplanovu přístupu. Pořád bojuje za nějakou teoretickou čistotu v Nette, ale v praxi všichni víme, že je nepoužitelná. Prasit se bude vždy, Hosiplan to dělá, jen na to nemá koule to hodit do Kdyby. (hosiplan/nette-autowire-properties)
Tady bych se s dovolením důrazně ohradil!
- Zpohodlnění presenterů mám v plánu dát do Kdyby – jen není čas
- Teoretickou čistotu praktikuju dnes a denně a nepřekvapivě funguje i v praxi
Jak už jsem opakoval snad 100×
- V presenterech service locator, nebo nějaká forma autowire – proč ne.
- V modelech? Nikdy.
Je naprosto legitimní vyžadovat lazy přístup:
Souhlasím.
Automatické proxy mi přijde jako totální blbost a magie.
Pokud napíšeš třídu čistě, tak ne. Na druhou stranu, nejsem fanda proxy patternu, ale má svoje místo například v Doctrine. Tady bych si tím nebyl úplně jistý.
Accessory mi přijdou jako dobré řešení
Accessory jsou zbytečné. Tam kde objekt bude „heavy“ (například PDO), tak ho owrapuju do třídy, které předám například jen pár stringů s údaji pro připojení do databáze (pro PDO) a vytvořím ho až když je potřeba. To že se PDO dědí je prostě chyba návrhu, která v prvopočátku nikoho nenapadla, nebo mu nijak extra nevadila. Na druhou stranu, reálně stránek, které databázi nepotřebují je naprosté minimum.
Vzor dekorátor určitě znáš.
cyklická závislost:
… třeba UsersFacade a ArticlesFacade. …
Co místo cyklické závislosti vytvořit třetí objekt, který bude řešit věci, u kterých jsi se nedokázal rozhodnout, kam je dát? Závislost ti zmizí. Klidně vytvářej tisíce tříd, já to tak dělám a funguje to ;)
Reálné příklady, kdy se tomu nejde vyhnout (narazil jsem na ně), řeším pak service locatorem. Ale těch je minimum. Navíc to vždy napíšu tak, aby se to konfigurovalo, jako by se žádný service locator nepoužíval, ale je pouze optimalizací, která mimo jiné vyřešila i cyklickou závislost.
Čím víc budu mít specializovaných malých tříd, tím více bude dominový efekt zřetelnější a méně předvídatelný. Prosím uvědomte si to.
No a ono je to přesně naopak, když si umíš graf objektů navrhnout.
@inject
na public property je naprosto čistý.
Souhlasím.
Automatický accessor je naprosto čistý.
Tomuhle bohužel též nejde nic vytknout. Na druhou stranu, jeho použití se jde vždy vyhnout a proto ho nepovažuji za best practice ani za fallback practise :P
Editoval Filip Procházka (13. 3. 2013 16:30)
- Tharos
- Člen | 1030
David Grudl:
1. S tímhle tvrzením bych byl opatrný. Mně obojí přijde jako něco docela jiného.
A tu výrokovou logiku jsi trochu obrátil, nemyslíš? :) Škodovky jsou podmnožinou aut. Nemám rád Škodovky ⇒ nemám rád auta? :)
Nemám jedinou námitku proti vzoru factory, mám jen námitky proti accessoru. A necítím se nijak schizofrenní.
2. Navrhuji z diskuze úplně vypustit úvahy nad
PDO
. Kdo v dnešní frameworkové době přímo vytváří
instanci PDO
?
- redhead
- Člen | 1313
No když už budu chtít lazy nad PDO, tak si ten accessor můžu napsat sám. Přijde mi, že tu obhajuješ accessor jen kvůli PDO a ničemu jinému, protože PDO je prostě fuj a dlouho se vytváří.
Kdybych přece jen chtěl mít rozhraní pro PDO je nejjednoduší podědit nebo udělat kompozici do vlastní třídy a udělat vlastní interface. Beng! a můžu mít proxy. A zas tolik práce to nedá.
EDIT: @Hosiplan to napsal dřív a lépe.
Editoval redhead (13. 3. 2013 15:21)
- redhead
- Člen | 1313
@Hosiplan: také nejsem fanda proxy, protože mě to štve psát. Pokud je ale Nette bude generovat, tak ani nebudu vědět, že nějaké jsou! Pro mě je to daleko lepší případ než Accessor. Neboť o tom budu vědět a budu ho muset používat (místo objektu, který chci).
Co se týče Factory tříd, Davide, já je v kódu používám jen minimálně (myslím tedy vlastní továrny), např. pro výrobu objektu ze složitější polymorfické struktury, který je definovaný nějakým klíčem (většinou se uvnitř skrývá switch).
Btw. kde jste vůbec vzali tento Accessor pattern? Nevzpomínám si, že bych o něm četl v knihách, a Google ví jen o getter metodách (což je mimochodem jiný výraz pro accessor – tedy ten zaběhnutý výraz, jehož smysl tu nabývá nových rozměrů). Pokud to vychází jen z oněch článků od Davida, tak mi přijde, že David řeší něco, co už pár let existuje a funguje to, ale dělá to po svém a blbě.
Pokud bych chtěl optimalizovat výkon, tak v případě Nette bych se díval jinam než zrovna na lazy vytváření objektů (já vím, že odpovědí je „pošli pull-request“).
EDIT: V článku se píše, že testování vede k lepšímu návrhu. Ehm? Teď, když budu chtít testovat s (třeba mocknutou) závislostí, budu muset vytvořit accessor, který mi tuto závislost teprve vrátí. Místo toho abych jen udělal mock a předal.
Editoval redhead (13. 3. 2013 15:57)
- redhead
- Člen | 1313
Další case, který mluví proti accessorům: nějaká závislost se nyní
vytváří dlouho nebo je jinak náročná. Po pár měsících refaktoruji
vnitřní kód nebo použiji jinou implementaci a vytváření se stane
zanedbatelně náročné – v tuto chvíli už je tedy lazy inicializace
pomocí accessorů zbytečná. Jenže všude, kde teď třídu používám budu
mít kód $depAccessor->get()->do();
a budu muset přepisovat
mnohem víc kódu (odstranit všechny get()
volání, měnit type
hinty, přejmenovávat proměnné – všude, VŠUDE). Hell!
- Tharos
- Člen | 1030
redhead: Tvoje velmi trefné postřehy narážejí v důsledku na něco, co se kdysi také řešilo ve flame pod Davidovo články (ve kterých se zrodil pojem „accessor“, není to žádný vzor). Má presenter (respektive nějaká modelová třída) rozhodovat o tom, jakým způsobem vznikne služba, kterou potřebuje? Část lidí tvrdila, že rozhodně ne, David tvrdil, že klidně ano. Řadím se do první skupiny – tohle přece má být presenteru úplně fuk.
- redhead
- Člen | 1313
Jistě, proč by to měl vědět? Presenteru to má bejt naprosto fuk. On totiž sám neví, co je a co není potřeba inicializovat lazy, co je a co není náročné. Stejně tak i modelové třídy. To ví přece někdo jiný a jsme zpátky u poučky DI, tak jak ji trefně napsal ve svém článku David.
Pokud se něco změní a lazy inicializace nebude potřeba, nastává stejný
hell (vlastně o dost větší) jako u ne-DI aplikace, kde je všude
nadrátované new Class
a my potřebujeme najednou použít
Class2
.
- David Grudl
- Nette Core | 8227
Jistě, proč by to měl vědět? Presenteru to má bejt naprosto fuk. On totiž sám neví, co je a co není potřeba inicializovat lazy, co je a co není náročné.
Zcela souhlasím a nikdy jsem netvrdil opak. Co naopak objekt ví, tak že určitou službu potřebuje jen občas (třeba při vzniku chyby apod.). Ovšem ten, kdo objekt vytváří, netuší, zda služba bude potřeba, to se totiž ukáže až později.
Situaci lze řešit tak, že
- objekt to neřeší a služba se předá vždycky, protože režie na její vytvoření je menší, než jiné cesty
- objekt to neřeší a předá se proxy, neboť režie na vytvoření služby je řádově větší
- objekt to řeší a místo služby si nechá předat továrnu, která, až v případě potřeby, službu vyrobí
Všechny tři cesty jsou zcela korektní a podle situace zvolím tu správnou. Dogmatické odsuzování (ať už pod zmíněnými články nebo tady) některé z cest je fakt hloupé.
A nakonec: pojem accessor slouží jen pro odlišení situace, kdy továrna nedovoluje opakované vytváření nového objektu a pracuje se stále s týmž objektem. Že není jinde používán není rozhodně moje chyba. Preferuju přesnější názvosloví.
- David Grudl
- Nette Core | 8227
A pokud chcete konkrétní příklad, tak třeba Latte se načítá, instancuje a konfiguruje až ve chvíli, kdy se při vykreslování šablon zjistí, že některá z nich není zkompilovaná v cache. Což je třeba v jednom případě z milionu požadavků.
- redhead
- Člen | 1313
Davide promiň, ale nic z toho, co si teď napsal, neargumentuje proti výše uvedeným důvodům proti accessorům. To, že se Latte instanciuje a konfiguruje až když je potřeba je správně. Proti tomu nic nemám, stejně tak nic nemám obecně proti lazy inicializaci (pokud je nutná a ty případy samozřejmě jsou, jako třeba právě Latte). Ale accessory, tak jak si je podal ty, jsou prostě špatná cesta.
Je vidět, že je tu i jakési pokřivené myšlení ohledně továren. Pokud má nějaká třída závislost na objektu, který má něco vytvářet a třída s tím dále pak pracuje, je správné použít továrnu. Pokud je, ale k činnosti třídy potřeba objekt (a je jedno kdy a jestli vůbec) tak předám prostě ten objekt, na kterém třída závisí (je už jedno jestli uvnitř je lazy inicializace – proxy – nebo není, nezajímá mě to). Třída nebude závislá na nějakém middle-man, ale bude závislá na objektu, který skutečně potřebuje (vždy, někdy, nebo vůbec, to už je jedno). Je lépe testovatelná a je průhlednější.
Ještě jednou, je tu rozdíl mezi závislostí na „továrně na vytváření objektu“ a na samotném „objektu“.
Příklad: pokud mám presenter, který vyrábí články, je asi dobré mít
na články továrnu, ať už kvůli nezadrátování new Article
v kódu, nebo i pro počáteční konfiguraci (např. datum publikování). To
je naprosto legitimní důvod pro použití továrny. Ne však, pokud presenter
potřebuje k běhu službu, která vrací seznam všech článků, které chci
zobrazit. Pak chci předat nějakou fasádu. Nikoliv továrnu na fasádu
(přejmenovanou na accessor). To je prostě špatně, nehledě na důvody,
které jsem řekl výše.
Pokud je tvorba té fasády drahá, obalím ji do proxy a k žádné změně nedojde (nikde v kódu).
Obávám se také, že to bude vést k předčasné optimalizaci – radši udělám lazy vše od začátku, abych případně nemusel přepisovat celou aplikaci, když bych musel později použít accessor kvůli pomalé třídě.
ad dogma: Je to jakoby si řekl, že všichni ti, co píšou knihy o designu a architektuře aplikací, nikdy opravdovou aplikaci nenaprogramovali, že se furt jen pídí po tom ultimátním dobru, čistotě kódu a akademické správnosti a nekoukají na praktičnost či použitelnost v dlouhodobém horizontu. Omyl.
Editoval redhead (13. 3. 2013 20:19)
- jasir
- Člen | 746
Jiný pohled na accessor je, že je to vlastně service lokátor – pro jednu konkrétní službu. A to je moc šikovné (protože x-krát mám chuť si ten container poslat :-P )
Pokud mám problém, že se mi nelíbí
$this->myServiceAccessor->get()
, obalím si přístup
do `
function getMyService()
{
return $this->myServiceAccessor->get();
}
Což *stejně obyčejně dělám*. Rozdíl proti factory a Accessor je ten, že nemusím psát:
function getMyLazyService() {
if ($this->myLazyService === NULL) {
$this->myLazyService = $this->myLazyServiceFactory->create();
}
return $this->myLazyService;
}
Já jsem pro.
Editoval jasir (13. 3. 2013 20:24)
- jasir
- Člen | 746
redhead napsal(a):
Ještě jednou, je tu rozdíl mezi závislostí na „továrně na vytváření objektu“ a na samotném „objektu“.
Ano. A pak můžu mít závislost na service lokátoru (čti – accessoru) – protože lazy, protože to není továrna (objekt se vytvoří jen jednou). Zcela ok.
Editoval jasir (13. 3. 2013 20:25)
- redhead
- Člen | 1313
jasir napsal(a):
Ano. A pak můžu mít závislost na service lokátoru (čti – accessoru) – protože lazy, protože to není továrna (objekt se vytvoří jen jednou). Zcela ok.
Já ale nechci mít třídu závislou na service lokátoru, ten já pro běh
mého objektu nepotřebuji. Třídu chci mít závislou na objektu, se kterým
potřebuju pracovat. Service lokátor je další (viditelný) middle-man stejně
tak jako je teď context
.
- David Grudl
- Nette Core | 8227
btw, accessor existuje v Nette už dávno. Stačí v příkladu CD-collection přidat rozhraní
interface DatabaseAccessor
{
/** @return Nette\Database\Connection */
function create();
}
zaregistrovat je do konfigu
nette:
database:
dsn: "sqlite:%appDir%/model/demo.db3"
factories:
db:
implement: DatabaseAccessor
create: @Nette\Database\Connection
a použít třeba v Authenticator.php:
public function __construct(DatabaseAccessor $database)
{
$this->database = $database->create();
}
- stekycz
- Člen | 152
Anotace @inject
pro public
atributy určitě.
Jsem pro Accessory za následujících podmínek:
- Accessor mi znatelně ušetří psaní kódu oproti proxy nebo vlastní implementaci.
- Někde v dokumentaci bude jediná stránka, na které bude srozumitelně popsáno jejich použití a použití i dalších zde uvedených postupů, včetně rozlišení, kdy a jak má být který použit. I kdyby jen v Planette. To by začátečníkům mohlo pomoci.
Navíc začátečník bude mít podle mě mnohem více jiných věcí, které bude a může prasit.
- redhead
- Člen | 1313
David: Nahradil si předání závislosti, kterou autentikátor potřebuje, za něco co tu závislost vytváří. Tedy ze závislosti na objektu si udělal závislost na dodavateli toho objektu. V tomhle případě to ještě tolik nepálí. Kdybys ale tu databázi teď použil i v jiných třídách, budeš ji v každé zase vytvářet? Proč? Proč se o to vytváření starají tyto třídy?
Editoval redhead (13. 3. 2013 20:54)
- Filip Procházka
- Moderator | 4668
@redhead: David pomocí „triku“ docílil, že se použije jiná service jako factory a vždy se proto vrací stejná instance (protože ta service už ji má vytvořenou a DIC si ji drží).
Editoval Filip Procházka (13. 3. 2013 21:00)
- redhead
- Člen | 1313
Ok, zajímavé názvosloví objektu vracející jedinou instanci (factory a
create), sorry prolít jsem to moc rychle, čekal bych slovo singleton (ne
nemyslím statický singleton). Nicméně, proč tedy rovnou nepředat onu
instanci a musí se volat nějaká metoda create()
, která mě od
závislosti bůhví proč odstiňuje? Vytváří se snad lazy? Zajímá mě to?
Ne. Proxy? Jsme tam kde jsme byli.
Mimochodem, boží argument pro ospravedlnění accessorů, že už to v Nette funguje!
Pořád čekám, až vypadne nějaký pádný argument.
Editoval redhead (13. 3. 2013 21:18)
- David Grudl
- Nette Core | 8227
redhead napsal(a):
Pořád čekám, až vypadne nějaký pádný argument.
Už jsem všechno sepsal, víc toho ode mě nebude. Snad jen dodám, že „obalit do proxy“ se lehce napíše, ale lze toho dosáhnout jen za cenu značných kompromisů a prakticky vůbec ne ručně.
- maryo
- Člen | 15
Taky mi přijde proxy pohodlnější.
Co jsou ty značný kompromisy? Já bych reflexí prošel public metody tý
třídy kterou by ta proxy zastupovala a implementoval bych stejný rozhraní.
Nejspíš mi teda něco nedochází.
EDIT: Je fakt, že už by to ale nebyla instance tý stejný třídy, to by se muselo udělat děděním a tam je problém s final (jak třídy, tak metod), ale to je potom asi jediná překážka, ne?.
Editoval maryo (14. 3. 2013 0:23)
- redhead
- Člen | 1313
Pokud tím chceš ospravedlnit špatný návrh, tak prosím. Mělo by to napravovat automaticky Nette? Neřekl bych. (mimochodem, přidat interface pro třídu, na kterou nemůžu šáhnout, je prkotina)
Stejně si myslím, že takových případů, kdy je potřeba ať už proxy nebo accessor má být (a zpravidla taky je, pokud neprasím presenter) minimum a tedy, že i automatické generování (proxy nebo accessoru) je zbytečná feature. Já ani accessor ani proxy v Nette ještě nepoužil a žádný bottle-neck se mi nikde nevyskytl. Po optimalizaci výkonu bych pátral jinde, protože tady problém není, nebo není tak závažný. A když už bude jó potřeba, tak si proxy vyrobím ručně.
- redhead
- Člen | 1313
Takže chceš nahradit divnost bez interface, kterou by napsal ručně uživatel, za jinou divnost, kterou vygeneruje Nette. Nehledě na to, že případů, kdy je to potřeba, je minimum. Nějak mi nedochází, proč je tohle v Nette potřeba. A pokud uživatel nedokáže navrhnout aplikaci správně nebo nemá znalosti, tak nejspíš i accessory bude používat blbě (viz co psal Filip na začátku). Snaha byla, máš svou hlavu.
- David Grudl
- Nette Core | 8227
Já tvrdím, že uživatel je vůbec používat nebude, proč by dělal něco extra složitě? Ale už fakt nemám chuť diskutovat, řekneme si za rok.
- paranoiq
- Člen | 392
David Grudl napsal(a):
Ano, takových případů je naprosté marginální minimum.
tady jsem se nějak zacyklil. zdá se mi, že debata jde stále dokola…
- Filip Procházka
- Moderator | 4668
Já bych to viděl tak, že každý řekl svůj názor, David si to nakonec implementoval sám a čas ukáže, jestli to byl dobrý nápad :)
- David Grudl
- Nette Core | 8227
Diskuse je o tom, že předat si továrnu, která vrací pokaždé nový objekt, je ok, ale předat si továrnu, která vrací stále tentýž objekt, je čiré zvěrstvo ;)