it-swarm.it

Quando è appropriato Singleton?

Alcuni sostengono che Singleton Pattern sia sempre un anti-pattern. Cosa ne pensi?

67
Fishtoaster

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 capaci e quindi tutto diventa un singleton e si vede 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.

32
Noah Goodrich

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 l'unità del codice, è necessario micro-gestire ed essere consapevoli dello stato globale nel singleton, per ottenere risultati di test coerenti.

42
Magnus Wolffelt

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 una posizione, 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.

34
dsimcha

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 del perché la gente lo usava.

  1. Singolo 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. Singolo istanziazione.

    "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 es. 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". Per 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. Istanziazione 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 principio di responsabilità singola , perché la classe fa controlla la propria istanza e ciclo di vita , oltre alle altre responsabilità che il 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é è semplicemente 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 piccolo contenitore del servizio locale 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 persino determinare dinamicamente quale classe usare.

7
donquixote

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 voglio 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. Se non è possibile che lo stato globale sia inizializzato una volta e non sia mai cambiato.

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

1
Brian R. Bondy