Je singleton mrtvý? Ale kdepak...

Odpověď Davidovi na článek a dlouhou diskusi o smyslu singletonu.

Disclaimer: Toto měl být komentář do diskuse, bohužel Davidův Nucleus mě nemá rád a moje komentáře zahazuje. Do včerejška ale aspoň řekl, že jsem ho vložil, dal na něj odkaz a po čase ho ukázal, dnes už ani náhodou. Tak proto komentuju tady. Původní komentář je pravděpodobně v nenávratnu, takže ho píšu znovu... V textu se obracím přímo na Davida, proto ta dialogová forma.

Ráno moudřejší večera. I já jsem, pravděpodobně, přes noc pochopil, co máš, Davide, za výhrady. Oprav mne, pokud se mýlím:

Tvou hlavní výhradou je Nedělejte zbytečně singletony, uzavíráte si tím cestu v případě, že později zjistíte, že se vám druhá instance hodí. Pokud potřebujete globálně přístupnou instanci, řešte ji globálním úložištěm, nebojte se globálních věcí, ale používejte je tam kde mají smysl a opodstatnění.

V podstatě totéž lze říct o singletonu: Používejte ho tam, kde má smysl a opodstatnění. Člověk, který použije singleton jen proto, že je globálně dostupný, si zadělává na problémy a často narazí na omezení, které si sám způsobil (a které je to hlavní, Davide, nikoli vedlejší efekt, jak píšeš).

Nemůžu souhlasit s tím, že "obhájci singletonu zpětně dohledávají důvody, proč musí být singleton jen jednou". Vezmu to na sebe, ale myslím, že ostatní vývojáři to mají podobně: Davide, pokud navrhuju aplikaci, tak nad tím přemýšlím a snažím se ji navrhnout tak, aby v budoucnu udělal návrh co nejmíň problémů a aby byl připraven na rozšiřování a na změny. Pokud v návrhu vidím, že potřebuju globálně dostupnou instanci třídy, použiju buď globální úložiště, nebo třeba Factory, pokud mi nezáleží jaká ta instance bude. Pokud v návrhu vidím, že potřebuju jednu a právě jednu instanci, použiju singleton.

Ty to nazýváš "odvážným". Píšeš: "Z hlediska návrhu aplikace je odvážné přisoudit objektu/třídě vlastnost, že může existovat jen jednou. (...) Ale v realitě se pak ukáže, že jde spíš o nedomyšlení věci." Řekl bych, Davide, že je odvážné tvrdit, že navržení singleton třídy je většinou znakem nedomyšlení věcí... Děláš snad z návrhářů, co narazí na nezbytnost pouze jedné instance, hlupáky, kteří nedokáží myslet dál než za první update? Jako bys říkal "Opravdu jen jedna instance? Fakt jste si jistí? Když si to ještě jednou rozmyslíte, zjistíte, že takováhle nutnost je jen fikce..." To přeci ne... :)

Jestli tomu dobře rozumím, tvá hlavní výhrada je proti tomu "jednu a právě jednu". Vypadá to, jako bys pochyboval o samotném smyslu onoho požadavku "maximálně jedné instance". Já za sebe mohu říct, že jsem v návrhu několikrát singletony použil, a použil jsem je tehdy, když mi bylo jasné, že druhá instance téže třídy by znamenala zmatky. A než bych riskoval její náhodné vytvoření, tak jsem raději zakázal. Věř, že to bylo po zralé úvaze, že jsem si tu aplikaci představoval na cizím serveru, na více serverech, s více databázemi, po pěti změnách, a když jsem opravdu nabyl dojmu, že jedinečnost je axiomatická, tak jsem ji použil.

Davide, je to podobná situace jako např. označit třídu jako Final (Sealed). Mohu použít stejný argument co ty: Zbořil by se svět, kdybych od Final třídy odvodil jinou? Stejná je odpověď i na tvou otázku "Zbořil by se svět, kdyby singletonová třída měla dvě instance?"

Píšeš, že třeba v budoucnu se může ukázat, že bude druhá instance potřebná. Na to ti mohu odpovědět buď stejnou razantní větou proti final třídě ("je nesmysl dělat final třídy, protože v budoucnu se může ukázat jako vhodné vytvořit odvozenou třídu...")

Davide, všechny tyhle výhrady neukazují na nesmyslnost konkrétní konstrukce (singleton, final, private proměnná + getter), ale jsou záležitostí návrhu a architektury. Z analýzy ti vypadnou nějaké požadavky na třídy a ty, jako návrhář, musíš poznat, kde má smysl omezit kreativitu a kde by omezení bylo zbytečné. Jistě poznáš rozdíl mezi "U téhle třídy bude tak nejvejš jedna instance", "U téhle třídy bude pravděpodobně jedna instance" a "u této třídy nesmí být víc než jedna instance". Kdo použije singleton u prvních dvou případů, zadělá si na problém stejně jako ten, kdo v třetím případě singleton NEpoužije.

Pokud se snad stane, že v průběhu realizace přijde požadavek na změnu, který nelze vyřešit jinak než "druhou instancí singletonu", tak to znamená jednu z následujících možností:

- Původní návrh je špatný a je potřeba jej změnit

- Změna není kompatibilní s původním návrhem, je potřeba jej změnit.

A pokud byl původní návrh a analýza pečlivá, tak je v takovém případě lepší celé reanalyzovat a navrhnout znovu už se započítáním oné změny. Věř, že se to vyplatí a smysl to má. Pokud se v původním návrhu jevila třída Kolo tak, že MUSÍ mít jen jednu instanci, a teď najednou MUSÍ být druhá instance, tak se muselo změnit něco opravdu fundamentálního a je lepší to celé zkouknout znovu, než vynadat na "blbý omezující singleton". Situace je to stejná, jako když přijde změna, kvůli které si říkáš: Potřebuju podědit FINAL třídu! Opravdu? Přece jsi měl nějaké důvody, proč jsi ji jako FINAL deklaroval. Copak ty důvody onou změnou pominuly? OPRAVDU je potřeba dědit, nebo je to tak, že to je v dané chvíli to nejjednodušší?

Pokud je potřeba druhá instance singletonu, tak je to buď problém s původním návrhem, nebo jen programátorova pohodlnost a uhýbání od návrhu a "coding guidelines".

---

Plně a naprosto s tebou souhlasím v tom, že používat singleton  jen kvůli jeho globální dostupnosti je naprosto nesmyslné. To, co je jako singleton prezentováno v mnohých populárních materiálech o PHP, je odhadem tak třetina celé problematiky. Singleton opravdu není vzor "globálně dostupné instance, co je jaksi mimochodem jen jedna", ale "třída, co musí mít jen jednu instanci, která je jaksi mimochodem globálně dostupná". Pokud někdo používá singleton v tom druhém smyslu, tak dělá chybu a "zatlouká šroub závažím". Ale v tom si, mám dojem, plně rozumíme a naprosto se shodujeme.

---

Dovol ještě detail. Píšeš: "K singletonům se váže problém, na který narazíme nejpozději při testování kódu. A tím je potřeba podstrčit jiný, testovací objekt. (...) Jednou z možností je implementovat statickou metodu setInstance($mockObj). Ale ouha! Copak asi chcete slečně metodě předat, když žádná jiná instance, než ona jedna jediná, neexistuje? (...) Jakýkoliv pokus odpovědět na tuto otázku povede nevyhnutelně k rozpadu všeho, co singleton dělá singletonem"

Davide, zde se buď mýlíš nebo jsem tvou výhradu nepochopil. Singleton může v getInstance() vracet "cokoli". Může vracet klidně instance odvozené třídy. Singleton můžeš udělat jako abstraktního společného "rodiče" různých (podobných) "jedináčků". Ale může klidně vrátit instanci jiné třídy, která implementuje patřičný interface. Není s tím problém.

---

A závěrečné prohlášení, na krterém se pravděpodobně shodneme: Ono Argumentum Aurum a poselství všehomíru je tedy zhruba toto: Lidi, pokud potřebujete globálně dostupnou instanci, tak kvůli tomu nemusíte dělat singletony! A v tom máš naprostou pravdu a já se připojuju: „Nedělejte singletony, když nevíte, v čem je jejich síla, a nebojte se použít radši s rozumem globální instance, pokud potřebujete globální instanci! Nebo alespoň globální fond / úložiště."

Dne 23.08.2008

Twittni

Přidej do: asdf.sk StumbleUpon Toolbar Stumble It!

Komentáře

[1] (m-a - WWW) 23.08.2008, 14:54:20 [X] [D]
Ostatne - singleton sa nevolá singleton, len preto, že sa autorom to slovo páčilo...
A teda, že dôraz je hlavne na slove single...

[2] (David Grudl - WWW) 23.08.2008, 14:57:04 [X] [D]
Bývaly časy, kdy jsi mi říkal Davídku... Ani to, že na mě přes noc myslíš, to nezachrání. Zřejmě letos nikde nejsou kytky.

:-))

Ten odstavec "Nedělejte zbytečně singletony..." je naprosto výstižný, přesně tak jsem to myslel. V podstatě tím by se dalo téma uzavřít. Jen ještě hodím na blog článek, ve kterém ukážu příklad "ideálního" použití singletonu, kdy v návrhu jasně vidím, jak píšeš, že potřebuju jednu a právě jednu instanci - no a pak ukážu, jak to bylo krátkozraké. Příběh bude ukončen rituální sebevraždou návrháře a jeho zvoláním "fakt jsem byl blbej!"


Záměrně jsem v článku nenapsal, že singleton je zlo a nesmí se používat - snažil jsem se vztyčit varovný prst a říct - hele, většinou, když Obstojný Programátor šáhne po singletonu, tak šáhl blbě.

S tou finální třídou (nebo finální metodou) je to velmi dobrý příměr! Rozdíl je v tom, že mylné final se dá napravit odstatněním jednoho klíčového slova, bez nutnosti měnit návrh aplikace, proto si to nezaslouží článek.

ad Singleton může v getInstance() vracet "cokoli": tady jsou právě tři problémy. 1) nikdy nevíš, kdy voláš getInstance() poprvé, abys jí řekl, co chceš vracet. 2) když budeš chtít rozšířit getInstance(), aby to nějak dokázala udělat, tak musíš editovat *cizí* třídu singletonu, nikoliv vlastní Factory 3) nelze to navázat na interface, protože IResponse::getInstance() nelze implementovat.

[3] (danaketh - Mail ) 23.08.2008, 15:53:00 [X] [D]
Ehm, pardon že nabourávám diskusi ale mohl by mi někdo doporučit literaturu na zmíněné téma? Ideálně právě pro PHP. Zjišťuju že místy se vůbec nechytám :/

[4] (bukaJ - Mail - WWW) 23.08.2008, 16:54:37 [X] [D]
[3] Souhlas... zaprášené literatury na PHP4 pochybné kvality je všude habaděj, ale kvalitní tištěná literatura o PHP5, a podobných vychytávkách... nějaká rada zkušených?

Dodám, že dokumentace, RFC ani jiné elektronické zroje mě neuspokojí, jako skutečná kniha rozšířena o "slovní úlohy".

[5] (rarouš [openID] - Mail - WWW) 23.08.2008, 17:02:12 [X] [D]
[2] Ale nikdo tě při návrhu Sigletonu neomezuje na statickou factory metodu. Můžeš použít factory třídu nebo ještě lépe nějaký kontejner, který se bude starat o život našeho jedináčka.

Pokud potřebuješ testovat kód který přímo používá nějakej singleton, je správné, aby tento kód věděl, že jde o singleton a měl recept jak si ho má vyrobit? Není lepší když ho dostane už upečenej z venku? ;)

Pak snadno můžeš podstrkávat mocky a stuby...

[6] (rarouš [openID] - Mail - WWW) 23.08.2008, 17:07:02 [X] [D]
[3] [4] pro začátek si přečtěte cokoli od GoF, nebo Martina Fowlera. Nejde ani tak o ukázky kódu, ale o myšlenky. Když si dáte hledat PHP design patterns na Googlu nebo Amaznu, taky nemůžete minout...

[7] (David Grudl - WWW) 23.08.2008, 17:24:11 [X] [D]
[5] to je pravda, narážíš na nepřesnost, které jsem se záměrně dopustil - totiž pod pojmem singleton naprostá většina (PHP) programátorů rozumí implementaci + užití, jejíž ukázkou jsem pro sichr článek otevřel a která na tebe vypadne, když si odklikneš prvních dvacet výsledků hledání "singleton" v Google. Pokud chápeš věc v širších souvislostech, tak už nejsi cílovou skupinou mého článku, tobě už nemá co říct, ty už to víš :-)

[8] (David Grudl - WWW) 23.08.2008, 18:59:00 [X] [D]
Slíbený lidský příběh o dvou singletonech se právě objevil na mém blogu http://phpfashion.com...ton-sofie-s - slabší povahy varuju, stříká z toho ženský sekret!

[9] (Arthur Dent [openID] - Mail - WWW) 23.08.2008, 19:41:15 [X] [D]
[3][4] Spíš než knihy od GoF, které místy šustí "akademičností" doporučuju opravdu velmi dobrou knihu "Návrhové vzory" od Rudolfa Pecinovského - napsané to je formou dialogu "žák - učitel", kdy žák klade otázky podobné těm, co tu okolo padají, a učitel vysvětluje co, proč a jak.

[10] (Arthur Dent [openID] - Mail - WWW) 23.08.2008, 20:03:19 [X] [D]
[8] Příběh pěkný, ale chybný. Chyba je tam v tu chvíli, kdy Sofie zjistí, že miluje Chulia a že Sabrininy intriky prokouknul don Alvarez a odjel proto i s diamanty do Acapulca. Sofie neměla vymýšlet serializaci tak, jak ji implementovala; měla si přečíst stránku 116 ve výše zmíněné knize a věděla by, kde že udělala chybu. A ty to už teď jistě víš taky.

Drobná nápověda: V Javě mají "magic method" readResolve(). Vypadá skoro jako PHP __wakeup(), je volána v téže situaci, ALE!!! pokud systém deserializuje objekt, kde je tato metoda, tak návratovou hodnotu této metody vrátí jako načtený objekt. Tak systém předejde tomu, aby vznikli dva jedináčci.

Hezky k tomu Pecinovský poznamenává: "O této možnosti se v příručkách Javy běžně nedočteš a v česky psaných už vůbec ne".

A Sofiina volba byla, na rozdíl od té filmové, chybná. Problém (zase) nebyl v singletonu, ale v tom, že jej Sofie chybně použila.

Stále totéž: To, že PHP má podobné nectnosti neznamená, že návrh, v němž je singleton, je špatný, pomýlený nebo krátkozraký.

Komisař Depreux seděl ve své kanceláři a potřásal hlavou. "Jojo, "říkal si... "Holka nešťastná. Ona ví, že dělá s jazykem, kde nelze singleton jednoduše serializovat, ví že tam nejsou jazykové prostředky na to, aby to udělala bezpečně, ale jde a serializuje ho! Blázen mladá... asi zamilovaná!" Pak si dlouze vzdychl a zhluboka se napil z láhve Pernodu.

[11] (Arthur Dent [openID] - Mail - WWW) 23.08.2008, 20:12:53 [X] [D]
[2] Davídka do toho netahej, to je ještě malé dítě! :)

Jinak: "S tou finální třídou (nebo finální metodou) je to velmi dobrý příměr! Rozdíl je v tom, že mylné final se dá napravit odstatněním jednoho klíčového slova, bez nutnosti měnit návrh aplikace, proto si to nezaslouží článek."

Jsem rád žes to takhle napsal. Zeptám se tě: To, že bude třída "final", je co? Je to momentální popud, jako že "asi teda... finální... kdyžtak pak uvidíme", nebo to je rozhodnutí, které vyplynulo z analýzy, v návrhu bylo zváženo a nakonec bylo rozhodnuto, že tato třída NESMÍ mít odvozeniny?

Pokud platí to první, tak nemá smysl se o nějaké metodice bavit, pak je to prostě jen plácání: "Uděláme to všechno privátní a finální a pak kdyžtak uvidíme". Ale pokud rozhodnutí udělat třídu jako "final" opravdu vyplynulo z analýzy a z návrhu, tak co je "odstranění jednoho klíčového slova"? Je to zásadní změna proti návrhu? Určitě ano, protože popírá důvody, které při analýze vedly k rozhodnutí udělat ji final.

Davide, obojí má stejnou váhu. Jak rozhodnutí o singletonu, tak rozhodnutí o final. Obojí musí vzejít z důkladné rozvahy při analýze a obojí je závažné (sebe)restriktivní omezení. A pokud se v průběhu vývoje zdá, že je nutné tuto restrikci zrušit, tak je putna, jestli to je "smazáním slova" nebo jestli musíš změnit víc metod, protože v obou případech to je popření nějakého závěru z analýzy. Pak je potřeba zjistit, jestli byla chyba v prvotní úvaze nebo zda je chyba v implementaci - každopádně je dobré vrátit se do toho bodu, kde padlo ono rozhodnutí "uděláme to s restrikcí", zvážit opět důvody a promyslet důsledky takové změny!

[12] (David Grudl - WWW) 23.08.2008, 21:23:15 [X] [D]
[10][11] serializace není pointou. Pointou je to, že i v případě, kdy nemá smysl, aby objekt měl více instancí, kdy je to podloženo pečlivou úvakou návrháře, který není hlupák, kdy by druhá instance téže třídy mohla znamenat jen zmatky, tak i tehdy může jít jen o nedomyšlení věci.

[13] (Arthur Dent [openID] - Mail - WWW) 23.08.2008, 21:29:43 [X] [D]
[12] Ale že byla ta pointa dobře schovaná, mrška, co? Já jsem ji třeba nenašel, a furt ji tam nevidím :)

[14] (Bronislav Klučka - Mail - WWW) 25.08.2008, 15:00:26 [X] [D]
[12] tohle je koukani za 5 rohu. Co kdyz zjistite, ze PHP byla spatna volba a potrebuje napsat vlastni server, ktery bude mit neustale prostupe promenne? Co kdyz zjistite, ze nasadit aplikaci na linux/windows je blbost a musite ji strcit na windows/linux? Co kdyz, co kdyz... to je hloupust, pokud po peclive uvaze a dukladnem navrhu, ktery prosel kontrolou nekoho, kdo se na nem nepodilel dojdou vsichni k zaveru, ze singleton je resenim, potom je resim, premyslet nad 'co kdyz' je kontraproduktivni.

Navic zapominate na rozvoj projektu. Pokud ke zmene dojde z duvodu evolucniho kroku, je to znamenim spatneho navrhu, ale o tom se nebavime. Pokud ke zmene dojde z duvodu revolucniho kroku, je zmena poradku. Neni mozne hned vedet vsechny okolnosti, ktere nastanou pro dalsi major verzi. Pokud bychom takhle premysleli, nenaprogramujeme nikdy nic.

[15] (Rike ) 08.09.2008, 19:18:33 [X] [D]
Osobně netuším, jak by v nějakém MVC webovém modelu chtěl David Grudl jinak reprezentovat třídu Request a třídu Response než jako singleton. Ke zvalidovaným hodnotám $_GET a $_POST je dobré mít přístup přece odevšud, vždyť ta pole jsou sama globální, ne?