Sui modelli di design: quando dovrei usare il singleton?


448

La variabile globale glorificata - diventa una classe globale gloriosa. Alcuni dicono che rompono il design orientato agli oggetti.

Dammi scenari, oltre al buon vecchio logger in cui ha senso usare il singleton.


4
Da quando ho imparato erlang, preferisco quell'approccio, ovvero l'immutabilità e il passaggio dei messaggi.
Setori,

209
Cosa non è costruttivo riguardo a questa domanda? Di seguito vedo una risposta costruttiva.
MK12,

3
Un framework di iniezione di dipendenza è un singleton molto complesso che distribuisce oggetto ...
Ian Ringrose,

1
Singleton può essere utilizzato come oggetto manager tra le istanze di altri oggetti, quindi dovrebbe esserci solo un'istanza del singleton in cui ogni altra istanza dovrebbe comunicare tramite l'istanza singleton.
Levent Divilioglu,

Ho una domanda a margine: qualsiasi implementazione Singleton può anche essere implementata usando una classe "statica" (con un metodo "factory" / "init") - senza effettivamente creare un'istanza di una classe (si potrebbe dire che una classe statica è un tipo di implementazione Singleton, ma ...) - perché si dovrebbe usare un Singleton reale (una singola istanza di classe che si assicura che sia singola) invece di una classe statica? L'unica ragione che mi viene in mente è forse per la "semantica", ma anche in questo senso, i casi d'uso di Singleton non richiedono davvero una relazione "class-> istanza" per definizione ... quindi ... perché?
Yuval A.

Risposte:


358

Nella mia ricerca della verità ho scoperto che in realtà ci sono pochissime ragioni "accettabili" per usare un Singleton.

Un motivo che tende a ripetersi all'internet è quello di una classe di "registrazione" (che hai menzionato). In questo caso, un Singleton può essere utilizzato al posto di una singola istanza di una classe perché una classe di registrazione di solito deve essere utilizzata più e più volte alla nausea da ogni classe in un progetto. Se ogni classe utilizza questa classe di registrazione, l'iniezione di dipendenza diventa ingombrante.

La registrazione è un esempio specifico di Singleton "accettabile" perché non influisce sull'esecuzione del codice. Disabilita la registrazione, l'esecuzione del codice rimane la stessa. Abilitalo, lo stesso. Misko lo inserisce nel modo seguente in Causa principale dei singoli , "Le informazioni qui fluiscono in un modo: dall'applicazione al logger. Anche se i logger sono stati globali, dal momento che nessuna informazione scorre dai logger all'applicazione, i logger sono accettabili".

Sono sicuro che ci sono anche altri validi motivi. Alex Miller, in " Patterns I Hate ", parla di localizzatori di servizi e di UI lato client che potrebbero anche essere scelte "accettabili".

Leggi di più su Singleton Ti amo, ma mi stai buttando giù.


3
@ArneMertz Immagino sia questo .
Attacco del

1
Perché non puoi semplicemente usare un oggetto globale? Perché deve essere un singleton?
Scarpa

1
Penso che il metodo statico per un programma di utilità di registrazione?
Skynet,

1
I singoli sono i migliori quando devi gestire le risorse. Ad esempio, connessioni HTTP. Non vuoi stabilire 1 milione di client http per un singolo client, che è pazzesco dispendioso e lento. Quindi un singleton con un client http in pool di connessione sarà molto più veloce e facile da usare.
Cogman,

3
So che questa è una vecchia domanda e le informazioni contenute in questa risposta sono ottime. Tuttavia, ho difficoltà a capire perché questa è la risposta accettata quando l'OP ha specificato chiaramente: "Dammi scenari diversi dal buon vecchio logger in cui ha senso usare il singleton".
Francisco C.

124

Un candidato Singleton deve soddisfare tre requisiti:

  • controlla l'accesso simultaneo a una risorsa condivisa.
  • l'accesso alla risorsa verrà richiesto da più parti disparate del sistema.
  • può esserci un solo oggetto.

Se il tuo Singleton proposto ha solo uno o due di questi requisiti, una riprogettazione è quasi sempre l'opzione corretta.

Ad esempio, è improbabile che uno spooler di stampa venga chiamato da più di una posizione (il menu Stampa), quindi è possibile utilizzare i mutex per risolvere il problema di accesso simultaneo.

Un semplice logger è l'esempio più ovvio di un Singleton possibilmente valido, ma questo può cambiare con schemi di registrazione più complessi.


3
Non sono d'accordo con il punto 2. Il punto 3 non è in realtà un motivo (solo perché non può significare che si dovrebbe) e 1 è un buon punto, ma non riesco ancora a vederlo. Supponiamo che la risorsa condivisa sia un'unità disco o una cache db. Puoi aggiungere un'altra unità o avere una cache db focalizzata su un'altra cosa (come una cache per una tabella specializzata per un thread con l'altro più generico).

15
Penso che ti sia sfuggita la parola "candidato". Un candidato Singleton deve soddisfare i tre requisiti; solo perché qualcosa soddisfa i requisiti, non significa che dovrebbe essere un Singleton. Potrebbero esserci altri fattori di progettazione :)
Metao

45

Leggere i file di configurazione che devono essere letti solo al momento dell'avvio e incapsularli in un Singleton.


8
Simile a Properties.Settings.Default.NET.
Nick Bedford,

9
@Paul, Il "campo no-singleton" indicherà che l'oggetto di configurazione dovrebbe essere semplicemente passato a funzioni che ne hanno bisogno, invece di renderlo accessibile a livello globale (aka singleton).
Pacerier,

2
Disaccordo. Se la configurazione viene spostata nel database, tutto è rovinato. Se il percorso della configurazione dipende da qualcosa al di fuori di quel singleton, anche queste cose devono essere statiche.
rr-

3
@PaulCroarkin Puoi approfondire questo e spiegare come questo è benefico?
AlexG

1
@ rr- se la configurazione si sposta nel database, può ancora essere incapsulata in un oggetto di configurazione che verrà passato alle funzioni che ne hanno bisogno. (PS Non sono nel campo "no-singleton").
Will Sheppard

36

Si utilizza un singleton quando è necessario gestire una risorsa condivisa. Ad esempio uno spooler per stampante. L'applicazione deve avere un'unica istanza dello spooler per evitare richieste contrastanti per la stessa risorsa.

O una connessione al database o un file manager ecc.


30
Ho sentito questo esempio di spooler di stampante e penso che sia un po 'zoppo. Chi dice che non posso avere più di uno spooler? Che diamine è comunque uno spooler di stampa? Cosa succede se ho diversi tipi di stampanti che non possono essere in conflitto o utilizzare driver diversi?
1800 INFORMAZIONI

6
È solo un esempio ... per qualsiasi situazione che chiunque usi come esempio, sarai in grado di trovare un design alternativo che renda l'esempio inutile. Supponiamo che lo spooler gestisca una singola risorsa condivisa da più componenti. Funziona.
Vincent Ramdhanie,

2
È il classico esempio per la banda di quattro. Penso che una risposta con un vero caso d'uso provato sarebbe più utile. Intendo una situazione in cui hai sentito che Singleton è la soluzione migliore.
Andrei Vajna II,

Secondo me una risorsa condivisa è un esempio troppo ampio. Come testereste che gli oggetti che utilizzano lo spooler di stampa funzionino correttamente di fronte a uno spooler malfunzionante quando non è possibile iniettare un'implementazione malfunzionante di 'spooler'? sebbene breve e poco informativa la risposta accettata è un approccio molto più sicuro nel mio libro
Rune FS

Che diamine è uno spooler per stampante?
RayLoveless,

23

Sono leggibili solo i singoli punti in cui sono memorizzati alcuni stati globali (lingua dell'utente, percorso file della guida, percorso dell'applicazione). Fai attenzione a usare i singleton per controllare la logica aziendale - il single finisce quasi sempre per essere multiplo


4
La lingua dell'utente può essere singleton solo supponendo che solo un utente possa utilizzare il sistema.
Samuel Åslund

... e quell'utente parla solo una lingua.
extra

17

Gestire una connessione (o un pool di connessioni) a un database.

Lo userei anche per recuperare e archiviare informazioni su file di configurazione esterni.


2
Un generatore di connessione al database non sarebbe un esempio di Factory?
Ken,

3
@Ken vorresti che quella fabbrica fosse un singleton in quasi tutti i casi.
Chris Marisic,

2
@Federico, Il "campo no-singleton" indicherà che queste connessioni al database dovrebbero semplicemente essere passate a funzioni che ne hanno bisogno, invece di renderle accessibili a livello globale (aka singleton).
Pacerier,

3
Non hai davvero bisogno di un singleton per questo. Può essere iniettato.
Nestor Ledon,

11

Uno dei modi in cui usi un singleton è quello di coprire un'istanza in cui deve esserci un unico "broker" che controlla l'accesso a una risorsa. I singleton sono buoni nei logger perché mediano l'accesso, per esempio, a un file, che può essere scritto esclusivamente. Per qualcosa come la registrazione, forniscono un modo per sottrarre le scritture a qualcosa come un file di registro: potresti avvolgere un meccanismo di memorizzazione nella cache per il tuo singleton, ecc ...

Pensa anche a una situazione in cui hai un'applicazione con molte finestre / thread / etc, ma che ha bisogno di un singolo punto di comunicazione. Una volta ne ho usato uno per controllare i lavori che volevo avviare la mia applicazione. Il singleton era responsabile della serializzazione dei lavori e della visualizzazione del loro stato a qualsiasi altra parte del programma che fosse interessata. In questo tipo di scenario, puoi vedere un singleton come una sorta di classe "server" in esecuzione all'interno della tua applicazione ... HTH


3
I logger sono spesso dei Singleton in modo da non dover passare oggetti di registrazione. Qualsiasi implementazione decente di un flusso di log garantirà l'impossibilità di scrivere simultaneamente, sia che si tratti di Singleton o meno.
Metao,

10

Un singleton dovrebbe essere usato quando si gestisce l'accesso a una risorsa condivisa dall'intera applicazione e sarebbe distruttivo avere potenzialmente più istanze della stessa classe. Fare in modo che l'accesso al thread delle risorse condivise sia sicuro è un ottimo esempio di dove questo tipo di modello può essere vitale.

Quando si utilizzano i Singleton, è necessario assicurarsi di non nascondere accidentalmente dipendenze. Idealmente, i singoli (come la maggior parte delle variabili statiche in un'applicazione) devono essere impostati durante l'esecuzione del codice di inizializzazione per l'applicazione (static void Main () per eseguibili C #, static void main () per eseguibili java) e quindi passati a tutte le altre classi istanziate che lo richiedono. Questo ti aiuta a mantenere la testabilità.


8

Penso che l'uso singleton possa essere pensato come lo stesso della relazione molti-in-uno nei database. Se hai molte parti diverse del tuo codice che devono funzionare con una singola istanza di un oggetto, è logico usare singleton.


6

Un esempio pratico di un singleton può essere trovato in Test :: Builder , la classe che supporta quasi ogni moderno modulo di test Perl. Test :: Builder singleton memorizza e negozia lo stato e la cronologia del processo di test (risultati dei test storici, conta il numero di test eseguiti) e cose come dove sta andando l'output del test. Questi sono tutti necessari per coordinare più moduli di test, scritti da autori diversi, per lavorare insieme in un unico script di test.

La storia del singleton di Test :: Builder è educativa. Chiamare new()ti dà sempre lo stesso oggetto. Innanzitutto, tutti i dati sono stati memorizzati come variabili di classe senza nulla nell'oggetto stesso. Funzionò fino a quando volevo testare Test :: Builder con se stesso. Quindi avevo bisogno di due oggetti Test :: Builder, un setup come un manichino, per catturare e testare il suo comportamento e output, e uno per essere il vero oggetto di test. A quel punto Test :: Builder è stato trasformato in un oggetto reale. L'oggetto singleton era archiviato come dati di classe e new()lo restituiva sempre. create()è stato aggiunto per creare un nuovo oggetto e abilitare i test.

Attualmente, gli utenti vogliono cambiare alcuni comportamenti di Test :: Builder nel proprio modulo, ma lasciarne altri da soli, mentre la cronologia dei test rimane in comune su tutti i moduli di test. Ciò che sta accadendo ora è che l'oggetto monolitico Test :: Builder viene suddiviso in pezzi più piccoli (cronologia, output, formato ...) con un'istanza Test :: Builder che li raccoglie insieme. Ora Test :: Builder non deve più essere un singleton. I suoi componenti, come la storia, possono essere. Ciò spinge la necessità inflessibile di un singleton di un livello. Offre maggiore flessibilità all'utente nel mescolare e abbinare i pezzi. Gli oggetti singleton più piccoli ora possono semplicemente archiviare i dati, con i loro oggetti di contenimento che decidono come usarli. Permette persino a una classe non Test :: Builder di suonare insieme usando la storia Test :: Builder e i singleton di output.

Sembra esserci una spinta e una spinta tra coordinamento dei dati e flessibilità del comportamento che possono essere mitigate mettendo il singleton attorno solo ai dati condivisi con il minor numero di comportamenti possibile per garantire l'integrità dei dati.


5

Quando si carica un oggetto Proprietà di configurazione, dal database o da un file, è utile averlo come singleton; non c'è motivo di continuare a rileggere i dati statici che non cambieranno mentre il server è in esecuzione.


2
Perché non caricare i dati una volta sola e passare l'oggetto di configurazione secondo necessità?
Lagweezle,

cosa succede con il passaggio ??? Se dovessi passare in giro ogni oggetto di cui avrei bisogno, avrei dei costruttori con 20 argomenti ...
Enerccio,

@Enerccio Se hai oggetti che si basano su altri 20 diversi senza incapsulamento, hai già grossi problemi di progettazione.
extra

@spectras Do I? Se implemento la finestra di dialogo della GUI, avrò bisogno di: repository, localizzazione, dati di sessione, dati dell'applicazione, widget widget, dati client, gestore delle autorizzazioni e probabilmente altro. Certo, puoi aggregarne alcuni, ma perché? Personalmente uso la primavera e gli aspetti per autorizzare tutte queste dipendenze nella classe dei widget e questo disaccoppia tutto.
Enerccio,

Se hai così tanto stato, potresti prendere in considerazione l'implementazione di una facciata, dando una visione degli aspetti rilevanti al contesto specifico. Perché? Perché consentirebbe un design pulito senza l'antipasto del costruttore singleton o 29-arg. In realtà il fatto stesso che la tua finestra di dialogo gui acceda a tutte quelle cose urla "violazione del principio della responsabilità singola".
extra il

3

Come tutti hanno detto, una risorsa condivisa, in particolare qualcosa che non può gestire l'accesso simultaneo.

Un esempio specifico che ho visto è uno scrittore di indice di ricerca Lucene.


1
Eppure IndexWriter non è un singleton ...
Mark

3

È possibile utilizzare Singleton quando si implementa il modello di stato (nel modo mostrato nel libro GoF). Questo perché le classi statali concrete non hanno uno stato proprio e svolgono le loro azioni in termini di classe contestuale.

Puoi anche rendere Abstract Factory un singleton.


Questo è il caso che sto affrontando ora in un progetto. Ho usato un modello di stato per rimuovere il codice condizionale ripetitivo dai metodi del contesto. Gli stati non hanno variabili di istanza proprie. Tuttavia, sono sul recinto per quanto riguarda se dovrei renderli singoli. Ogni volta che lo stato cambia, viene istanziata una nuova istanza. Ciò sembra dispendioso perché non è possibile che l'istanza sia diversa da un'altra (perché non esistono variabili di istanza). Sto cercando di capire perché non dovrei usarlo.
kiwicomb123,

1
@ kiwicomb123 Cerca di assumerti la setState()responsabilità di decidere la politica di creazione dello stato. Aiuta se il tuo linguaggio di programmazione supporta modelli o generici. Invece di Singleton, è possibile utilizzare il modello Monostate , in cui l'istanza di un oggetto stato finisce per riutilizzare lo stesso oggetto stato globale / statico. La sintassi per cambiare lo stato potrebbe rimanere invariata, poiché gli utenti non devono essere consapevoli del fatto che lo stato istanziato è un Monostato.
Emile Cormier,

Ok, quindi nei miei stati potrei semplicemente rendere statici tutti i metodi, quindi ogni volta che viene creata una nuova istanza non ha lo stesso overhead? Sono un po 'confuso, ho bisogno di leggere di più sul modello Monostate.
kiwicomb123,

@ kiwicomb123 No, Monostate non ha l'obiettivo di rendere statici tutti i membri. Meglio che tu lo legga, quindi controlla SO per domande e risposte correlate.
Emile Cormier,

Penso che questo dovrebbe avere più voti. La fabbrica astratta è abbastanza comune e poiché le fabbriche sono apolidi, stabili nell'apolidia e non possono essere implementate con metodi statici (in Java) che non sono sostituiti, l'uso di singleton dovrebbe essere corretto.
DPM,

3

Risorse condivise. Soprattutto in PHP, una classe di database, una classe modello e una classe di deposito variabile globale. Tutti devono essere condivisi da tutti i moduli / classi che vengono utilizzati in tutto il codice.

È un vero utilizzo dell'oggetto -> la classe template contiene il modello di pagina che viene creato e viene modellato, aggiunto, modificato dai moduli che si stanno aggiungendo all'output della pagina. Deve essere conservato come una singola istanza in modo che ciò accada, e lo stesso vale per i database. Con un singleton di database condiviso, tutte le classi dei moduli possono accedere alle query e ottenerle senza doverle rieseguire.

Un deposito di variabili globali singleton offre un deposito di variabili globale, affidabile e facilmente utilizzabile. Riordina molto il tuo codice. Immagina di avere tutti i valori di configurazione in un array in un singleton come:

$gb->config['hostname']

o con tutti i valori di lingua in un array come:

$gb->lang['ENTER_USER']

Alla fine dell'esecuzione del codice per la pagina, ottieni, diciamo, un ormai maturo:

$template

Singleton, un $gbsingleton che ha l'array lang da sostituire in esso e tutto l'output è caricato e pronto. Li sostituisci semplicemente nelle chiavi che sono ora presenti nel valore di pagina dell'oggetto modello maturo e poi lo distribuisci all'utente.

Il grande vantaggio di questo è che puoi fare QUALSIASI post-elaborazione che ti piace su qualsiasi cosa. Puoi reindirizzare tutti i valori della lingua a google translate o a un altro servizio di traduzione e ripristinarli e sostituirli nei loro luoghi, tradotti, ad esempio. oppure puoi sostituire le strutture di pagina o le stringhe di contenuto come desideri.


21
Potresti voler suddividere la tua risposta in più paragrafi e bloccare i segmenti di codice per leggibilità.
Giustino,

1

Può essere molto pragmatico configurare problemi specifici dell'infrastruttura come singoli o variabili globali. Il mio esempio preferito di ciò sono i framework di Iniezione delle dipendenze che fanno uso di singoli per fungere da punto di connessione al framework.

In questo caso si sta assumendo una dipendenza dall'infrastruttura per semplificare l'utilizzo della libreria ed evitare complessità non necessarie.


0

Lo uso per un oggetto che incapsula i parametri della riga di comando quando si tratta di moduli collegabili. Il programma principale non sa quali sono i parametri della riga di comando per i moduli che vengono caricati (e non sa nemmeno nemmeno quali moduli vengono caricati). ad esempio, i carichi principali A, che non hanno bisogno di alcun parametro (quindi perché dovrebbe richiedere un puntatore / riferimento extra / qualunque cosa, non sono sicuro - sembra inquinamento), quindi carica i moduli X, Y e Z. Due di questi, diciamo X e Z, necessitano (o accettano) dei parametri, quindi richiamano al singleton della riga di comando per dirgli quali parametri accettare, e in fase di esecuzione richiamano per scoprire se l'utente ha effettivamente specificato di loro.

In molti modi, un singleton per la gestione dei parametri CGI funzionerebbe allo stesso modo se stai usando un solo processo per query (altri metodi mod_ * non lo fanno, quindi sarebbe male lì - quindi l'argomento che dice che non dovresti ' usare singleton nel mondo mod_cgi nel caso in cui porti su mod_perl o in qualunque altro mondo).


-1

Un esempio con il codice, forse.

Qui, il ConcreteRegistry è un singleton in un gioco di poker che consente ai comportamenti fino all'albero del pacchetto di accedere alle poche interfacce di base del gioco (ovvero le facciate per il modello, la vista, il controller, l'ambiente, ecc.):

http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html

Ed.


1
Il collegamento ora è interrotto, ma se stai registrando le informazioni di visualizzazione in un singleton, a cui accederai in tutta l'applicazione, perdi il punto di MVC. Una vista viene aggiornata da (e comunica a) un controller, che utilizza il modello. Come sembra qui, è probabilmente un uso improprio di Singleton e un refactoring è in ordine.
drharris,

-9

1 - Un commento sulla prima risposta:

Non sono d'accordo con una classe Logger statica. questo può essere pratico per un'implementazione, ma non può essere sostituibile per test unitari. Una classe statica non può essere sostituita da un doppio di prova. Se non esegui il test unitario, non vedrai il problema qui.

2 - Cerco di non creare un singleton a mano. Creo semplicemente un oggetto semplice con costruttori che mi consentono di iniettare collaboratori nell'oggetto. Se avessi bisogno di un singleton, utilizzerei un framework di inection di dipendenza (Spring.NET, Unity per .NET, Spring per Java) o qualche altro.


11
È necessario commentare le risposte direttamente facendo clic sul collegamento in fondo a una risposta; è molto più facile da leggere in quel modo. Inoltre, la risposta che hai visto in alto probabilmente non è la prima. Le risposte vengono riordinate continuamente.
Ross,

perché dovresti desiderare la registrazione di unit test?
Enerccio,

Esiste un'enorme differenza tra "una classe Logger statica" e un'istanza Logger statica . Il modello singleton non dice "rendi statica la tua classe", dice per rendere statico l'accesso all'istanza di un oggetto. Ad esempio, ILogger logger = Logger.SingleInstance();dove questo metodo è statico e restituisce un'istanza memorizzata staticamente di un ILogger. Hai usato l'esempio di "un framework di iniezione di dipendenza". Quasi tutti i contenitori DI sono singoli; le loro configurazioni sono definite staticamente e infine accessibili da / archiviate in un'unica interfaccia del fornitore di servizi.
Jon Davis,
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.