Quando è appropriato Singleton? [chiuso]


Risposte:


32

Le due principali critiche a Singleton rientrano in due campi da ciò che ho osservato:

  1. I singleton vengono usati in modo improprio e maltrattati da programmatori meno abili e quindi tutto diventa un singleton e vedi il codice disseminato di riferimenti Class :: get_instance (). In generale, ci sono solo una o due risorse (come ad esempio una connessione al database) che si qualificano per l'uso del modello Singleton.
  2. I singleton sono essenzialmente classi statiche, che si basano su uno o più metodi e proprietà statici. Tutte le cose statiche presentano problemi reali e tangibili quando si tenta di eseguire il test unitario perché rappresentano vicoli ciechi nel codice che non possono essere derisi o cancellati. Di conseguenza, quando si verifica una classe che si basa su un Singleton (o qualsiasi altro metodo o classe statica) non si sta solo testando quella classe ma anche il metodo o la classe statica.

Come risultato di entrambi, un approccio comune consiste nell'utilizzare la creazione di un oggetto container ampio per contenere una singola istanza di queste classi e solo l'oggetto container modifica questi tipi di classi mentre a molte altre classi può essere concesso l'accesso da cui utilizzare l'oggetto contenitore.


6
Vorrei solo dire che puoi deridere i metodi statici in Java con JMockit ( code.google.com/p/jmockit ). È uno strumento molto utile.
Michael K,

3
Alcuni pensieri: il contenitore è un Singleton, quindi non ti sei sbarazzato di Singletons. Inoltre, se stai scrivendo una libreria con un'API pubblica, non puoi forzare l'utente dell'API a utilizzare il tuo contenitore. Se hai bisogno di un gestore delle risorse globale nella tua biblioteca (proteggere l'accesso a una singola risorsa fisica, ad esempio), devi usare un Singleton.
Scott Whitlock,

2
@Scott Whitlock perché deve essere un singleton? Solo perché c'è una sola istanza non lo rende così. Qualsiasi libreria IoC gestirà questo tipo di dipendenza ...
MattDavey,

Questa soluzione l'ha proposta anche conosciuta come Toolbox
cregox,

41

Sono d'accordo che si tratta di un anti-pattern. Perché? Perché consente al tuo codice di mentire sulle sue dipendenze e non puoi fidarti che altri programmatori non introducano uno stato mutabile nei tuoi singleton precedentemente immutabili.

Una classe potrebbe avere un costruttore che accetta solo una stringa, quindi pensi che sia istanziato in isolamento e non abbia effetti collaterali. Tuttavia, silenziosamente, sta comunicando con una sorta di oggetto singleton pubblico a livello globale, in modo che ogni volta che crei un'istanza della classe, contenga dati diversi. Questo è un grosso problema, non solo per gli utenti della tua API, ma anche per la testabilità del codice. Per testare correttamente il codice dell'unità, è necessario micro-gestire ed essere consapevoli dello stato globale nel singleton, per ottenere risultati di test coerenti.


Vorrei poter votare più di una volta!
MattDavey,

34

Il modello Singleton è fondamentalmente solo una variabile globale inizializzata pigramente. Le variabili globali sono generalmente e giustamente considerate malvagie perché consentono un'azione spettrale a distanza tra parti di un programma apparentemente non correlate. Tuttavia, in IMHO non c'è nulla di sbagliato nelle variabili globali che vengono impostate una volta, da un punto, come parte della routine di inizializzazione di un programma (ad esempio, leggendo un file di configurazione o argomenti della riga di comando) e successivamente trattati come costanti. Tale uso delle variabili globali è diverso solo nella lettera, non nello spirito, dall'avere una costante nominata dichiarata al momento della compilazione.

Allo stesso modo, la mia opinione su Singletons è che sono cattivi se e solo se vengono utilizzati per passare lo stato mutevole tra parti apparentemente non correlate di un programma. Se non contengono uno stato mutabile o se lo stato mutabile che contengono è completamente incapsulato in modo che gli utenti dell'oggetto non debbano conoscerlo nemmeno in un ambiente multithread, non c'è nulla di sbagliato in loro.


3
Quindi, in altre parole, sei contrario a qualsiasi uso di un database in un programma.
Kendall Helmstetter Gelner,

C'è un famoso video di discussione tecnica su Google che echeggia la tua opinione youtube.com/watch?v=-FRm3VPhseI
Martin York

2
@KendallHelmstetterGelner: esiste una differenza tra stato e memoria secondaria. È improbabile che tu possa contenere un intero DB in memoria.
Martin York,

7

Perché le persone lo usano?

Ho visto un certo numero di singoli nel mondo PHP. Non ricordo alcun caso d'uso in cui ho trovato il modello giustificato. Ma penso di avere un'idea della motivazione per cui la gente l'ha usata.

  1. Singola istanza .

    "Usa una singola istanza di classe C in tutta l'applicazione."

    Questo è un requisito ragionevole, ad es. Per la "connessione al database predefinita". Ciò non significa che non creerai mai una seconda connessione db, ma significa che di solito lavori con quella predefinita.

  2. Singola istanza .

    "Non consentire l'istanza di classe C più di una volta (per processo, per richiesta, ecc.)."

    Ciò è rilevante solo se l'istanza della classe avrebbe effetti collaterali che sono in conflitto con altre istanze.

    Spesso questi conflitti possono essere evitati riprogettando il componente, ad esempio eliminando gli effetti collaterali dal costruttore della classe. Oppure possono essere risolti in altri modi. Ma potrebbero esserci ancora alcuni casi d'uso legittimi.

    Dovresti anche pensare se il requisito "solo uno" significhi davvero "uno per processo". Ad esempio per la concorrenza delle risorse, il requisito è piuttosto "uno per intero sistema, tra processi" e non "uno per processo". E per altre cose è piuttosto per "contesto applicativo", e ti capita di avere un solo contesto applicativo per processo.

    Altrimenti, non è necessario applicare questa ipotesi. Applicare ciò significa anche che non è possibile creare un'istanza separata per i test unitari.

  3. Accesso globale.

    Ciò è legittimo solo se non si dispone di un'infrastruttura adeguata per passare gli oggetti nel luogo in cui vengono utilizzati. Ciò potrebbe significare che il tuo framework o ambiente fa schifo, ma potrebbe non essere in tuo potere risolverlo.

    Il prezzo è un accoppiamento stretto, dipendenze nascoste e tutto ciò che è negativo per lo stato globale. Ma probabilmente stai già soffrendo questi.

  4. Istanza pigra.

    Questa non è una parte necessaria di un singleton, ma sembra il modo più popolare per implementarli. Ma, sebbene l'istanza pigra sia una cosa carina da avere, non hai davvero bisogno di un singleton per raggiungerlo.

Implementazione tipica

L'implementazione tipica è una classe con un costruttore privato, una variabile di istanza statica e un metodo getInstance () statico con istanza lazy.

Oltre ai problemi sopra menzionati, questo morde con il principio della singola responsabilità , perché la classe controlla il proprio ciclo di vita e istanza , oltre alle altre responsabilità che la classe ha già.

Conclusione

In molti casi, è possibile ottenere lo stesso risultato senza singleton e senza stato globale. Invece, dovresti usare l'iniezione di dipendenza e potresti prendere in considerazione un contenitore di iniezione di dipendenza .

Tuttavia, ci sono casi d'uso in cui rimangono i seguenti requisiti validi:

  • Istanza singola (ma non singola istanza)
  • Accesso globale (perché stai lavorando con un framework dell'età del bronzo)
  • Un'istanza pigra (perché è bello avere)

Quindi, ecco cosa puoi fare in questo caso:

  • Crea una classe C che desideri creare un'istanza, con un costruttore pubblico.

  • Creare una classe S separata con una variabile di istanza statica e un metodo S :: getInstance () statico con istanza lazy, che utilizzerà la classe C per l'istanza.

  • Elimina tutti gli effetti collaterali dal costruttore di C. Inserisci invece questi effetti collaterali nel metodo S :: getInstance ().

  • Se si dispone di più di una classe con i requisiti di cui sopra, è possibile considerare di gestire le istanze di classe con un contenitore di servizi locale di piccole dimensioni e utilizzare l'istanza statica solo per il contenitore. Quindi, S :: getContainer () ti darà un contenitore di servizi con istanza pigra e otterrai gli altri oggetti dal contenitore.

  • Evita di chiamare la getInstance () statica dove puoi. Utilizzare invece l'iniezione di dipendenza, quando possibile. In particolare, se si utilizza l'approccio contenitore con più oggetti che dipendono l'uno dall'altro, nessuno di questi dovrebbe chiamare S :: getContainer ().

  • Facoltativamente, creare un'interfaccia implementata dalla classe C e utilizzarla per documentare il valore di ritorno di S :: getInstance ().

(Lo chiamiamo ancora singleton? Lascio questo alla sezione dei commenti ..)

Benefici:

  • È possibile creare un'istanza separata di C per unit test, senza toccare alcuno stato globale.

  • La gestione delle istanze è separata dalla classe stessa -> separazione delle preoccupazioni, principio di responsabilità unica.

  • Sarebbe abbastanza facile lasciare che S :: getInstance () utilizzi una classe diversa per l'istanza, o addirittura determinare dinamicamente quale classe usare.


1

Personalmente userò i singleton quando avrò bisogno di 1, 2 o 3, o di un numero limitato di oggetti per la particolare classe in questione. Oppure desidero comunicare all'utente della mia classe che non voglio che vengano create più istanze della mia classe affinché funzioni correttamente.

Inoltre lo userò solo quando dovrò usarlo quasi ovunque nel mio codice e non voglio passare un oggetto come parametro a ogni classe o funzione che ne ha bisogno.

Inoltre userò un singleton solo se non romperà la trasparenza referenziale di un'altra funzione. Il significato dato un certo input produrrà sempre lo stesso output. Cioè non lo uso per lo stato globale. A meno che non sia possibile che lo stato globale sia inizializzato una volta e non sia mai cambiato.

Per quanto riguarda quando non usarlo, vedere i 3 precedenti e cambiarli nel contrario.


3
-0 ma ho quasi -1. Preferirei passarlo ovunque nel mio codice invece di usare un singleton MA, se sono davvero pigro, potrei farlo. Userei invece varars globali se potessi o membri statici. i Var globali sono molto preferibili (per me) rispetto ai singoli. I globi non mentono
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.