V minulém díle jsme se bavili o situacích, kdy má třída příliš mnoho odpovědností. V dnešním díle si ukážeme, jak velké problémy skrývá na první pohled příjemný a jednoduchý návrhový vzor Singleton a proč jej tak mnoho programátorů zneužívá v situacích, kde nemá co dělat.
S návrhovým vzorem Singleton se mnoho programátorů již pravděpodobně setkalo. Pro zopakování: jeho záměr je následující (citát je z knihy Design Patterns, často zkráceně nazývána GoF book):
Mnoho programátorů se k tomuto návrhovému vzoru dostane právě přes onu GoF knihu. Zde bohužel musím souhlasit se Stevem, že většina lidí, kteří si onu knihu přečtou, si z ní odnesou toto: bla bla bla bla Singleton bla bla bla bla bla. Proč tomu tak je? Zřejmě z toho důvodu, že Singleton je pochopitelný i pro programátory, kteří zatím objektově orientovanému programování příliš nepřičichli a stále se drží procedurálního paradigma. Je to jakási záchrana pro lidi, kteří nechápou objektově orientované programování ani poslání oné GoF knihy. Tak si z ní odnesou alespoň Singleton. Jejich reakce je pak podobná této: "Wow, tenhle vzor chápu! To je tak jednoduché. No a jelikož je to návrhový vzor, tak bych ho měl začít pořádně využívat, ať ostatní vidí, že jsem ten správný skilled h4x0r programmer!"
Neberte mě špatně. I já tento návrhový vzor miloval a byl se za něj schopen bít. S postupujícím časem, jak se člověk dostane do objektově orientovaného programování, tak většinou procitne a uvědomí si, že Singleton je zlo a je to jeden z nejvíce zneužívaných návrhových vzorů. Mnoho lidí si totiž myslí, že do svého programu musí nacpat co nejvíce návrhových vzorů. A když jich znají jen pár, či pouze Singleton, tak se jej snaží používat i na místech, kde absolutně nemá co dělat. Zneužití jakéhokoliv návrhového vzoru vede ke špatnému návrhu. Za to ale daný vzor nemůže. Mohou za to programátoři.
Problémů při nevhodném použití tohoto návrhového vzoru vyvstane celá řada.
Je zajímavé, že většina programátorů z procedurálních jazyků ví, že globální proměnné jsou špatné a v Céčku by se jich štítili jako čert kříže. Ale když se dostanou k objektově orientovanému programování, jak jakoby jim to najednou přestalo vadit a Singletona začnou vesele používat. Zajímavé. Objeví se ale podobné obtíže, jako při použití "klasické" globální proměnné: snížení přehlednosti kódu, znovupoužitelnosti, vysoká provázanost (high coupling) a další. Však to znáte.
test1
a potom test2
, ale při použití Singletonu to nemusí být pravda. Problémem je stav Singletonu, který je sdílen všemi testy. Při typické implementaci Singletonu totiž máte jednu instanci pro celou aplikaci, tudíž pro všechny testy. Jeden test ale může změnit stav Singletonu a druhý s tím nemusí počítat. Musíte tedy nějak zajistit, že po skončení testu se stav Singletonu reinicializuje. Přidání metody reinitialize()
je však dost špatný nápad, pokud to děláte jen kvůli tomu, abyste kód mohli otestovat. Co když tu metodu začnou volat uživatelé vašeho Singletonu?Pokud navrhujete kód s tím, že má být testovatelný pomocí jednotkových testů, tak místo Singletonu automaticky zvolíte vhodnější řešení. Pokud ale máte netestovaný kód, který je prošpikován Singletony a který chcete testovat, pak vám přeji hodně štěstí a pevných nervů. Budete je potřebovat.
getInstance2()
? Tato situace typicky nastane, pokud začnete zpracovávat něco vícekrát zároveň a potřebujete k tomu Singleton. Pak si ona zpracování navzájem mění stav Singletonu a je s tím více problémů, než užitku. Málokdo si uvědomuje, že je rozdíl, zda vaše aplikace potřebuje pouze jednu instanci či zda může existovat pouze jedna instance. Použitím Singletonu si zbytečně zavíráte vrátka.getInstance()
), tak vaše třída najednou bude mít dvě odpovědnosti: (1) tu, kvůli které jste ji vytvářeli a (2) starání se o to, aby měla pouze jednu instanci. Typické porušení SRP.getInstance()
. Místospooler->addJob(job);
budete mít
Spooler::getInstance()->addJob(job);
Utrpí tím čitelnost vašeho kódu. Schválně si zkuste, kolikrát se ve vašem kódu vyskytuje řetězec "getInstance()"
.
Řešení je přímočaré: nepožívat Singleton na místech, kde nemá co dělat. Použití tohoto návrhového vzoru je totiž velmi limitované na speciální případy, které si programátor dokáže ospravedlnit a povede to u nich k lepšímu návrhu, než bez použití Singletonu. Pokud tápete nad tím, zda by na daném místě bylo použití Singletonu výhodné, pak je odpověď ne. Toho ale mnoho programátorů není schopno a pak to dopadá tak, že všude kam se podíváte, tak je Singleton.
Abych uvedl konkrétní příklad nahrazení Singletonu něčím vhodnějším, tak pokud potřebujete pouze jednu instanci, tak si danou třídu instanciujte ve vstupním bodu programu (typicky main
) a použijte dependency injection. Pokud tento vzor neznáte, tak se jej naučte, protože to je jeden z nejužitečnějších vzorů. Další tipy viz také odkazy níže.
Např. na následujících odkazech. Zřejmě vás překvapí, že na tento návrhový vzor tak moc lidí nadává, ale programátoři si za to mohou sami, když jej používají v situacích, kde nemá co dělat.
Nic vám ale nenahradí vlastní prozření, kdy sami na základě zkušeností pochopíte, že Singleton není ta správná cesta. Bude to těžké. Bude to bolet. Ale bude to stát za to.
Přidat komentář