it-swarm-eu.dev

Kdy je Singleton vhodný?

Někteří si myslí, že Singleton Pattern je vždy anti-pattern. Co myslíš?

67
Fishtoaster

Dvě hlavní kritiky Singletonů spadají do dvou táborů z toho, co jsem pozoroval:

  1. Singletons jsou zneužívány a zneužívány méně schopnými programátory, a tak se všechno stává singletonem a uvidíte kód posetý odkazy Class :: get_instance (). Obecně lze říci, že existují pouze jeden nebo dva zdroje (například připojení k databázi), které splňují podmínky pro použití vzoru Singleton.
  2. Singletons jsou v podstatě statické třídy, které se spoléhají na jednu nebo více statických metod a vlastností. Všechny věci statické představují skutečné, hmatatelné problémy, když se pokoušíte provádět testování jednotek, protože představují slepé konce v kódu, které nelze zesměšňovat nebo zakázat. Výsledkem je, že když testujete třídu, která se spoléhá na Singleton (nebo jakoukoli jinou statickou metodu nebo třídu), netestujete pouze tuto třídu, ale také statickou metodu nebo třídu.

V důsledku obou z nich je běžným přístupem použití vytvoření širokého kontejnerového objektu, který bude držet jednu instanci těchto tříd, a pouze kontejnerový objekt modifikuje tyto typy tříd, zatímco mnoha jiným třídám může být udělen přístup k nim, aby je mohl používat z objekt kontejneru.

32
Noah Goodrich

Souhlasím s tím, že se jedná o anti-model. Proč? Protože umožňuje vašemu kódu lhát o jeho závislostech a nemůžete věřit jiným programátorům, aby nezavedli mutabilní stav ve vašich dříve neměnných singletonech.

Třída může mít konstruktor, který vezme pouze řetězec, takže si myslíte, že je instalována izolovaně a nemá vedlejší účinky. V tichosti však komunikuje s jakýmsi veřejným globálně dostupným singletonovým objektem, takže kdykoli vytvoříte třídu, obsahuje různá data. To je velký problém nejen pro uživatele vašeho API, ale také pro testovatelnost kódu. Abyste mohli správně testovat kód, musíte mikro-spravovat a být si vědom globálního stavu v singletonu, abyste získali konzistentní výsledky testu.

42
Magnus Wolffelt

Singletonův vzorec je v podstatě jen líně inicializovaná globální proměnná. Globální proměnné jsou obecně a správně považovány za zlo, protože umožňují strašidelnou akci na vzdálenost mezi zdánlivě nesouvisejícími částmi programu. IMHO však není nic špatného s globálními proměnnými, které jsou nastaveny jednou, z jednoho místa, jako součást inicializační rutiny programu (například načtením argumentů konfiguračního souboru nebo příkazového řádku) a poté se s nimi zachází jako s konstantami. Takové použití globálních proměnných se liší pouze písmenem, nikoli duchem, od deklarování jmenované konstanty v době kompilace.

Stejně tak podle mého názoru na Singletons jsou špatné, a to pouze tehdy, jsou-li použity k předávání proměnlivého stavu mezi zdánlivě nesouvisejícími částmi programu. Pokud neobsahují mutabilní stav nebo pokud je mutabilní stav, který obsahují, zcela zapouzdřen, takže uživatelé objektu o něm nemusí vědět ani v prostředí s více vlákny, pak s nimi není nic špatného.

34
dsimcha

Proč to lidé používají?

Viděl jsem ve světě PHP) docela dost singletů. Nepamatuji si žádný případ použití, kde jsem shledal, že je vzor ospravedlněn. Ale myslím, že jsem dostal představu o motivaci, proč lidé to používali.

  1. Single instance.

    "V celé aplikaci použijte jednu instanci třídy C."

    Toto je přiměřený požadavek, např. pro „výchozí připojení k databázi“. To neznamená, že už nikdy nevytvoříte druhé připojení db, pouze to znamená, že obvykle pracujete s výchozím.

  2. Single instantiation.

    "Nedovolte instanci třídy C vícekrát (na proces, na žádost atd.)."

    To je relevantní pouze v případě, že by instancování třídy mělo vedlejší účinky, které jsou v rozporu s jinými případy.

    Těmto konfliktům lze často zabránit přepracováním komponenty - např. odstraněním vedlejších účinků od konstruktéra třídy. Nebo je lze řešit jinými způsoby. Stále však mohou existovat případy legitimního použití.

    Měli byste také přemýšlet o tom, zda požadavek „pouze jeden“ ve skutečnosti znamená „jeden na proces“. Např. pro souběžnost prostředků je požadavek spíše „jeden na celý systém, napříč procesy“, a nikoli „jeden na proces“. A pro jiné věci je to spíše za „aplikační kontext“ a v každém procesu máte jen jeden aplikační kontext.

    Jinak není třeba tento předpoklad prosazovat. Vynucení to také znamená, že nemůžete vytvořit samostatnou instanci pro testy jednotek.

  3. Globální přístup.

    To je legitimní, pouze pokud nemáte řádnou infrastrukturu pro předávání objektů kolem místa, kde jsou použity. To by mohlo znamenat, že váš rámec nebo prostředí je na hovno, ale nemusí to spadat do vaší pravomoci.

    Cena je těsná vazba, skryté závislosti a vše, co je špatné ohledně globálního stavu. Ale ty už pravděpodobně trpíš.

  4. Líná instanci.

    Nejedná se o nezbytnou součást singletonu, ale zdá se, že je nejoblíbenějším způsobem, jak je implementovat. Ale přestože je líná instinkce hezká věc, k dosažení toho opravdu nepotřebujete singleton.

Typická implementace

Typická implementace je třída se soukromým konstruktorem a proměnnou statické instance a statická metoda getInstance () s línou instancí.

Kromě výše zmíněných problémů se to kousne s princip jediné odpovědnosti , protože třída ano řídí svou vlastní instanci a životní cyklus , kromě dalších povinností, které třída již má.

Závěr

V mnoha případech můžete dosáhnout stejného výsledku bez singletonu a bez globálního stavu. Místo toho byste měli použít injekci závislosti a možná budete chtít zvážit kontejner závislosti na injekci .

Existují však případy použití, kdy zbývají následující platné požadavky:

  • Jedna instance (ale ne jediná instance)
  • Globální přístup (protože pracujete s rámcem doby bronzové)
  • Líná instanci (protože je prostě hezké mít)

V tomto případě tedy můžete udělat následující kroky:

  • Vytvořte třídu C, kterou chcete vytvořit, s veřejným konstruktorem.

  • Vytvořte samostatnou třídu S s proměnnou statické instance a statickou metodu S :: getInstance () s línou instancí, která použije instanci třídy C.

  • Odstraňte všechny vedlejší účinky z konstruktoru C. Místo toho vložte tyto vedlejší účinky do metody S :: getInstance ().

  • Pokud máte více než jednu třídu s výše uvedenými požadavky, můžete zvážit správu instancí třídy pomocí malý místní servisní kontejner a použít statickou instanci pouze pro kontejner. S :: getContainer () vám tedy poskytne lenivý instanční servisní kontejner a z kontejneru získáte další objekty.

  • Vyhněte se volání statického getInstance (), kde můžete. Místo toho použijte injekci závislosti, kdykoli je to možné. Zvláště, pokud používáte kontejnerový přístup s více objekty, které jsou na sobě závislé, pak žádný z nich by neměl mít volání S :: getContainer ().

  • Volitelně vytvořte rozhraní, které implementuje třída C, a použijte k zdokumentování návratové hodnoty S :: getInstance ().

(Stále to nazýváme singleton? Nechám to v sekci komentářů.)

Výhody:

  • Můžete vytvořit samostatnou instanci C pro testování jednotek, aniž byste se dotkli jakéhokoli globálního stavu.

  • Správa instance je oddělena od samotné třídy -> oddělení obav, princip jediné odpovědnosti.

  • Bylo by docela snadné nechat S :: getInstance () použít pro instanci jinou třídu, nebo dokonce dynamicky určit, kterou třídu použít.

7
donquixote

Osobně budu používat singletony, když budu potřebovat 1, 2 nebo 3 nebo nějaké omezené množství objektů pro danou třídu. Nebo chci sdělit uživateli své třídy, že nechci vytvořit více instancí své třídy, aby správně fungovala.

Také jej použiji, jen když ho potřebuji použít téměř všude ve svém kódu a nechci předávat objekt jako parametr každé třídě nebo funkci, která to potřebuje.

Kromě toho budu používat singleton pouze tehdy, pokud nenaruší referenční průhlednost jiné funkce. Význam při určitém vstupu bude vždy produkovat stejný výstup. Tj. Nepoužívám to pro globální stav. Není-li to možné, je tento globální stav jednou inicializován a nikdy se nezměnil.

Pokud jde o to, kdy jej nepoužívat, podívejte se na výše uvedené 3 a změňte je na opačný.

1
Brian R. Bondy