Singleton: come dovrebbe essere usato


291

Modifica: da un'altra domanda ho fornito una risposta che contiene collegamenti a molte domande / risposte sui singoli: Ulteriori informazioni sui singoli qui:

Quindi ho letto il thread Singletons: buon design o stampella?
E l'argomento infuria ancora.

Vedo Singletons come un modello di progettazione (buono e cattivo).

Il problema con Singleton non è il modello ma piuttosto gli utenti (scusate tutti). Tutti e il padre pensano di poterne implementare correttamente (e dalle molte interviste che ho fatto, molte persone non possono). Inoltre, poiché tutti pensano di poter implementare un Singleton corretto, abusano del Pattern e lo usano in situazioni non appropriate (sostituendo le variabili globali con Singletons!).

Quindi le domande principali a cui è necessario rispondere sono:

  • Quando dovresti usare un Singleton
  • Come si implementa correttamente un Singleton

La mia speranza per questo articolo è che possiamo raccogliere insieme in un unico posto (piuttosto che dover cercare su Google e più siti) una fonte autorevole di quando (e poi come) usare correttamente un Singleton. Sarebbe anche appropriato un elenco di anti-usi e comuni cattive implementazioni che spiegano perché non funzionano e per buone implementazioni i loro punti deboli.


Quindi fai rotolare la palla:
terrò la mia mano in alto e dirò che è quello che uso ma probabilmente ho problemi.
Mi piace "Scott Myers" che tratta l'argomento nei suoi libri "Effective C ++"

Buone situazioni per usare i Singleton (non molti):

  • Framework di registrazione
  • Pool di riciclaggio del filo
/*
 * C++ Singleton
 * Limitation: Single Threaded Design
 * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
 *      For problems associated with locking in multi threaded applications
 *
 * Limitation:
 * If you use this Singleton (A) within a destructor of another Singleton (B)
 * This Singleton (A) must be fully constructed before the constructor of (B)
 * is called.
 */
class MySingleton
{
    private:
        // Private Constructor
        MySingleton();
        // Stop the compiler generating methods of copy the object
        MySingleton(MySingleton const& copy);            // Not Implemented
        MySingleton& operator=(MySingleton const& copy); // Not Implemented

    public:
        static MySingleton& getInstance()
        {
            // The only instance
            // Guaranteed to be lazy initialized
            // Guaranteed that it will be destroyed correctly
            static MySingleton instance;
            return instance;
        }
};

OK. Consente di ottenere alcune critiche e altre implementazioni insieme.
:-)


36
E se in seguito decidessi di voler più logger? O più pool di thread? Se si desidera un solo logger, creare solo un'istanza e renderlo globale. I singleton sono buoni solo se hai BISOGNO che ci sia sempre solo uno e DEVI essere globale, IMHO.

3
Chi ha detto che un framework può avere solo 1 istanza del logger. Un singelton che rappresenta Framework. Framwork può quindi fornirti logger specifici.
Martin York,

Sì. Non userei un singolo come threadpool. Basta lanciare idee per scatenare risposte.
Martin York,

@Dan Singleton che implementa il modello di strategia. Il comportamento viene estratto da singleton. Singleton è un singolo punto di accesso. Non hai due logger, ne hai uno che può decidere come accedere. Non è possibile eseguire l'output solo su un registro alla volta, non è necessario averne due.
Lee Louviere,

5
Xaade: cosa succede se si desidera accedere a due file? O a un database? O un socket di rete? O un widget GUI? Il punto è, non aggiungere restrizioni artificiali - non è necessario. Quante volte hai mai creato accidentalmente due per loop anziché solo uno? Se si desidera un solo logger, crearne solo uno.

Risposte:


182

Tutti voi avete torto. Leggi la domanda Risposta:

Usa un Singleton se:

  • È necessario disporre di un solo oggetto di un tipo nel sistema

Non utilizzare un Singleton se:

  • Vuoi risparmiare memoria
  • Vuoi provare qualcosa di nuovo
  • Vuoi mostrare quanto sai
  • Perché lo fanno tutti gli altri (vedi il programmatore di culto del carico in wikipedia)
  • Nei widget dell'interfaccia utente
  • Dovrebbe essere una cache
  • In stringhe
  • In sessioni
  • Posso andare tutto il giorno

Come creare il miglior singleton:

  • Più piccolo è, meglio è. Sono un minimalista
  • Assicurarsi che sia sicuro per i thread
  • Assicurati che non sia mai nullo
  • Assicurati che sia stato creato una sola volta
  • Inizializzazione pigra o di sistema? Fino alle tue esigenze
  • A volte il sistema operativo o la JVM creano singleton per te (ad esempio in Java ogni definizione di classe è un singleton)
  • Fornire un distruttore o in qualche modo capire come smaltire le risorse
  • Usa poca memoria

14
In realtà, penso che neanche tu abbia ragione. Mi piacerebbe riformulare come: "Se necessario avere uno e un solo oggetto di un tipo di sistema e hanno bisogno di avere accesso globale ad essa" L'enfasi sul bisogno è mio - non farlo se la sua comoda, solo se si DEVE averlo.

91
Anche tu hai torto. Se hai bisogno di un solo oggetto, ne crei uno solo. Se non esiste un modo logico che due istanze possano mai essere sistemate senza corrompere irreversibilmente l'applicazione, dovresti considerare di renderla un singleton. E poi c'è l'altro aspetto, l'accesso globale: se non hai bisogno dell'accesso globale all'istanza, non dovrebbe essere un singleton.
jalf,

4
Chiuso per modifica, aperto per estensione. Il problema è che non puoi estendere un singleton per essere un duotone o un tripleton. È bloccato come un singleton.
Lee Louviere,

2
@ enzom83: una Singleton maiuscola include un codice per garantirne la singolarità. Se desideri solo un'istanza, puoi perdere quel codice e semplicemente crearne una tu stesso ... dandoti i risparmi di memoria di una singola istanza, oltre ai risparmi derivanti dall'opposizione del codice che impone la single-sign - che significa anche non sacrificare il possibilità di creare una seconda istanza se cambiano i requisiti.
cHao,

4
"Se hai bisogno di avere un solo oggetto di un tipo nel sistema" - "... e non vuoi mai deridere quell'oggetto in un unit test."
Cygon,

72

I singleton ti danno la possibilità di combinare due tratti negativi in ​​una classe. È sbagliato praticamente in ogni modo.

Un singleton ti dà:

  1. Accesso globale a un oggetto e
  2. Una garanzia che non è mai possibile creare più di un oggetto di questo tipo

Il numero uno è semplice. I globuli sono generalmente cattivi. Non dovremmo mai rendere gli oggetti accessibili a livello globale se non ne abbiamo davvero bisogno.

Il numero due può sembrare sensato, ma pensiamo a questo. Quando è stata l'ultima volta che ** hai accidentalmente * creato un nuovo oggetto invece di fare riferimento a uno esistente? Dato che questo è etichettato C ++, usiamo un esempio da quella lingua. Scrivi spesso per caso

std::ostream os;
os << "hello world\n";

Quando intendevi scrivere

std::cout << "hello world\n";

Ovviamente no. Non abbiamo bisogno di protezione contro questo errore, perché quel tipo di errore non si verifica. In tal caso, la risposta corretta è di andare a casa e dormire per 12-20 ore e sperare che tu ti senta meglio.

Se è necessario un solo oggetto, è sufficiente creare un'istanza. Se un oggetto deve essere accessibile a livello globale, rendilo globale. Ma ciò non significa che dovrebbe essere impossibile crearne altri esempi.

Il vincolo "solo un'istanza è possibile" non ci protegge realmente da possibili bug. Ma non rendere il nostro codice molto difficile da refactoring e mantenere. Perché abbastanza spesso scopriamo in seguito che avevamo bisogno di più di un'istanza. Abbiamo fare avere più di una banca dati, ci facciamo avere più di un oggetto di configurazione, noi vogliamo diverse logger. I nostri test unitari potrebbero voler essere in grado di creare e ricreare questi oggetti ogni test, per fare un esempio comune.

Quindi un Singleton deve essere utilizzato se e solo se, abbiamo bisogno di entrambi i tratti che offre: se abbiamo bisogno di un accesso globale (che è raro, perché globali sono generalmente scoraggiati) e abbiamo bisogno per evitare che qualcuno mai la creazione di più di un'istanza di un classe (che mi sembra un problema di progettazione). L'unica ragione per cui posso vedere questo è se la creazione di due istanze corromperà il nostro stato di applicazione, probabilmente perché la classe contiene un numero di membri statici o una stupidità simile. Nel qual caso la risposta ovvia è quella di sistemare quella classe. Non dovrebbe dipendere dall'essere l'unica istanza.

Se hai bisogno dell'accesso globale a un oggetto, rendilo globale, come std::cout. Ma non limitare il numero di istanze che è possibile creare.

Se hai assolutamente, positivamente bisogno di limitare il numero di istanze di una classe a una sola, e non c'è modo che la creazione di una seconda istanza possa mai essere gestita in modo sicuro, quindi imporla. Ma non renderlo accessibile anche a livello globale.

Se hai bisogno di entrambi i tratti, allora 1) rendilo singleton e 2) fammi sapere per cosa hai bisogno, perché sto facendo fatica a immaginare un caso del genere.


3
oppure potresti renderlo un globale e ottenere solo uno dei lati negativi di un singleton. Con il singleton, ti limiteresti simultaneamente a un'istanza di quel tipo di database. Perché farlo? Oppure potresti vedere perché hai così tante dipendenze che l'elenco delle istanze diventa "davvero lungo". Sono tutti necessari? Alcuni dovrebbero essere delegati ad altri componenti? Forse alcuni di essi potrebbero essere raggruppati in una struttura in modo da poterli passare come un singolo argomento. Ci sono molte soluzioni, tutte migliori dei singoli.
jalf

6
Sì, un singleton potrebbe essere giustificato lì. Ma penso che tu abbia appena dimostrato il mio punto che è necessario solo in casi piuttosto esotici. La maggior parte dei software non si occupa dell'hardware di spazzaneve. Ma non sono ancora convinto. Concordo sul fatto che nella tua domanda effettiva desideri solo uno di questi. Ma per quanto riguarda i test unitari? Ognuno di loro dovrebbe funzionare in modo isolato, quindi dovrebbe idealmente creare il proprio SpreaderController - che è difficile da fare con un singleton. Infine, perché i tuoi colleghi dovrebbero creare più istanze in primo luogo? È uno scenario realistico da proteggere?
jalf,

3
E un punto che ti sei perso è che mentre i tuoi ultimi due esempi probabilmente giustificano la limitazione "solo un'istanza", non fanno nulla per giustificare quella "accessibile a livello globale". Perché mai l'intera base di codice dovrebbe essere in grado di accedere all'unità di amministrazione dell'interruttore telefonico? Il punto in un singleton è quello di darti entrambi i tratti. Se hai solo bisogno dell'uno o dell'altro, non dovresti usare un singleton.
jalf,

2
@ jalf - Il mio obiettivo era solo quello di darti un esempio di dove Singleton è utile in natura, dal momento che non puoi immaginarlo; Immagino che non ti accorgerai più volte di applicarlo alla tua attuale linea di lavoro. Sono passato alla programmazione dello spazzaneve dalle applicazioni aziendali solo perché mi avrebbe permesso di utilizzare Singleton. :) j / k Sono d'accordo con la tua premessa che ci sono modi migliori per fare queste cose, mi hai dato molto su cui riflettere. Grazie per la discussione!
J. Polfer,

2
Usare il "modello" singleton ( AHEM! ) Per impedire alle persone di creare istanze in più istanze è semplicemente stupido solo per impedire alle persone di farlo accidentalmente. Quando ho una variabile locale foo1 di tipo Foo nella mia piccola funzione e ne voglio solo una nella funzione, non mi preoccupo che qualcuno creerà un secondo Foo varaible e utilizzarlo al posto di quello originale.
Thomas Eding,

36

Il problema con i singoli non è la loro implementazione. È che confondono due concetti diversi, nessuno dei quali è ovviamente desiderabile.

1) I singleton forniscono un meccanismo di accesso globale a un oggetto. Sebbene possano essere leggermente più sicuri o leggermente più affidabili nelle lingue senza un ordine di inizializzazione ben definito, questo uso è ancora l'equivalente morale di una variabile globale. È una variabile globale vestita con una sintassi scomoda (foo :: get_instance () invece di g_foo, diciamo), ma ha lo stesso identico scopo (un singolo oggetto accessibile attraverso l'intero programma) e ha gli stessi inconvenienti.

2) I singoli punti impediscono più istanze di una classe. È raro, IME, che questo tipo di funzionalità debba essere inserita in una classe. Normalmente è una cosa molto più contestuale; molte cose che sono considerate come una e una sola sono in realtà solo per essere una sola. IMO una soluzione più appropriata è creare solo un'istanza, fino a quando non ti rendi conto che hai bisogno di più di un'istanza.


6
Concordato. Due errori possono fare un diritto secondo alcuni, nel mondo reale. Ma nella programmazione, mescolando due cattive idee non si ottiene una buona idea.
jalf,

27

Una cosa con gli schemi: non generalizzare . Hanno tutti i casi in cui sono utili e quando falliscono.

Singleton può essere brutto quando devi testare il codice. In genere sei bloccato con un'istanza della classe e puoi scegliere tra l'apertura di una porta nel costruttore o un metodo per ripristinare lo stato e così via.

Un altro problema è che Singleton in realtà non è altro che una variabile globale sotto mentite spoglie. Quando hai troppo stato condiviso globale sul tuo programma, le cose tendono a tornare indietro, lo sappiamo tutti.

Potrebbe rendere più difficile il rilevamento delle dipendenze . Quando tutto dipende dal tuo Singleton, è più difficile cambiarlo, dividerlo in due, ecc. Di solito sei bloccato. Ciò ostacola anche la flessibilità. Indagare un quadro di Iniezione delle dipendenze per cercare di alleviare questo problema.


8
No, un singleton è molto più di una variabile globale sotto mentite spoglie. Questo è ciò che lo rende particolarmente cattivo. Combina la globalità (che di solito è cattiva) con un altro concetto che è anche cattivo (quello di non lasciare al programmatore un'istanza di una classe se decide di aver bisogno di un'istanza). Spesso sono usati come variabili globali, sì. E poi trascinano anche l'altro brutto effetto collaterale e paralizzano la base di codice.
jalf,

7
Va anche notato che i singoli non devono essere accessibili al pubblico. Un singleton può benissimo essere interno alla libreria e mai esposto all'utente. Quindi non sono necessariamente "globali" in questo senso.
Steven Evers,

1
+1 per indicare quanto dolore Singletons sta testando.
DevSolar

1
@jalf Non consentire a qualcuno di creare più di un'istanza di una classe non è una brutta cosa. Se esiste davvero un'istanza della classe solo un'istanza che impone il requisito. Se qualcuno decide in seguito che è necessario creare un'altra istanza, dovrebbe effettuare il refactoring, poiché in primo luogo non avrebbe mai dovuto essere un singleton.
William,

2
@William: e di tanto in tanto ho dovuto avere più logger. Non stai litigando per un singleton, ma per un semplice vecchio locale. Volete sapere che un logger è sempre disponibile. Ecco a cosa serve un globale. Non è necessario sapere che nessun altro logger può mai essere istanziato , ed è ciò che impone un singleton. (prova a scrivere unit test per il tuo logger - è molto più facile se puoi crearlo e distruggerlo secondo necessità, e ciò non è possibile con un singleton)
jalf

13

I singleton fondamentalmente ti consentono di avere uno stato globale complesso in lingue che altrimenti renderebbe difficile o impossibile avere variabili globali complesse.

Java in particolare usa i singleton in sostituzione di variabili globali, poiché tutto deve essere contenuto in una classe. Quanto più si avvicina alle variabili globali sono le variabili statiche pubbliche, che possono essere utilizzate come se fossero globaliimport static

Il C ++ ha variabili globali, ma l'ordine in cui vengono invocati i costruttori delle variabili di classe globali non è definito. Pertanto, un singleton consente di rinviare la creazione di una variabile globale fino alla prima volta in cui tale variabile è necessaria.

Linguaggi come Python e Ruby usano i singleton pochissimo perché invece puoi usare variabili globali all'interno di un modulo.

Quindi quando è buono / cattivo usare un singleton? Praticamente esattamente quando sarebbe bello / cattivo usare una variabile globale.


Quando mai una variabile globale è "buona"? A volte sono la soluzione migliore per un problema, ma non sono mai "buone".
DevSolar

1
la variabile globale è buona quando viene utilizzata ovunque e tutto può accedervi. Un'implementazione di una macchina turing a singolo stato può utilizzare un singleton.
Lee Louviere,

Mi piace il livello di riferimento indiretto in questa risposta: "quando sarebbe bello / cattivo usare un globale". Sia DevSolar che Lee Louviere ottengono il valore con cui concordano, anche se a tempo di risposta non si sapeva chi avrebbe commentato.
Prassolitico,

6

Il moderno design C ++ di Alexandrescu ha un singleton generico ereditabile e sicuro.

Per il mio valore di 2p, penso che sia importante avere una durata definita per i tuoi singoli (quando è assolutamente necessario usarli). Normalmente non consento alla get()funzione statica di creare un'istanza per nulla, lasciando l'impostazione e la distruzione in alcune sezioni dedicate dell'applicazione principale. Questo aiuta a evidenziare le dipendenze tra i singoli - ma, come sottolineato sopra, è meglio evitarli, se possibile.


6
  • Come si implementa correttamente un Singleton

C'è un problema che non ho mai visto menzionato, qualcosa che ho incontrato in un precedente lavoro. Avevamo singleton C ++ condivisi tra DLL e i soliti meccanismi per garantire che una singola istanza di una classe non funzionasse. Il problema è che ogni DLL ottiene il proprio set di variabili statiche, insieme a EXE. Se la funzione get_instance è incorporata o fa parte di una libreria statica, ciascuna DLL verrà caricata con la propria copia del "singleton".

La soluzione è assicurarsi che il codice singleton sia definito solo in una DLL o EXE o creare un gestore singleton con quelle proprietà per distribuire le istanze.


10
Yo dawg, ho sentito che ti sono piaciuti i Singleton, quindi ho creato un Singleton per il tuo Singleton, così puoi anti-pattern mentre sei anti-pattern.
Eva,

@Eva, sì qualcosa del genere. Non ho creato il problema, dovevo solo farlo funzionare in qualche modo.
Mark Ransom,

5

Il primo esempio non è thread-safe: se due thread chiamano getInstance contemporaneamente, tale statica sarà una PITA. Qualche forma di mutex sarebbe d'aiuto.


Sì, si nota nei commenti sopra: * Limitazione: design a thread singolo * Vedere: aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf * Per problemi associati al blocco in applicazioni multi-thread
Martin York,

Il singleton classico con solo getInstance come metodo statico e metodi di istanza per altre operazioni non può mai essere protetto da thread. (beh, a meno che tu non lo trasformi in una tonnellata singola per thread usando l'archiviazione locale dei thread ...)
Tobi,

anche in c ++ 11 o versioni successive?
hg_git,

5

Come altri hanno notato, i principali svantaggi dei singoli includono l'incapacità di estenderli e la perdita del potere di istanziare più di un'istanza, ad esempio a scopo di test.

Alcuni aspetti utili dei singoli:

  1. istanza pigra o anticipata
  2. utile per un oggetto che richiede installazione e / o stato

Tuttavia, non è necessario utilizzare un singleton per ottenere questi vantaggi. È possibile scrivere un oggetto normale che fa il lavoro e quindi consentire alle persone di accedervi tramite una factory (un oggetto separato). La fabbrica può preoccuparsi solo di istanziarne una, riutilizzarla, ecc., Se necessario. Inoltre, se si programma su un'interfaccia piuttosto che su una classe concreta, la fabbrica può utilizzare le strategie, ovvero è possibile attivare e disattivare varie implementazioni dell'interfaccia.

Infine, una fabbrica si presta a tecnologie di iniezione di dipendenza come Spring, ecc.


3

I singleton sono utili quando si esegue molto codice durante l'inizializzazione e l'oggetto. Ad esempio, quando si utilizza iBatis quando si configura un oggetto di persistenza, è necessario leggere tutte le configurazioni, analizzare le mappe, assicurarsi che sia tutto corretto, ecc. Prima di accedere al proprio codice.

Se lo facessi ogni volta, le prestazioni sarebbero molto degradate. Usandolo in un singleton, prendi quel colpo una volta e poi tutte le chiamate successive non devono farlo.


Anche il modello prototipo fa questo ed è più flessibile. Puoi anche usarlo quando il tuo client realizzerà molte istanze della tua classe costosa, ma solo un numero limitato di esse ha effettivamente uno stato diverso. Ad esempio, tetronimos in Tetris.
Eva,

3

La vera rovina di Singletons è che spezzano l'eredità. Non è possibile derivare una nuova classe per offrire funzionalità estese a meno che non si abbia accesso al codice a cui viene fatto riferimento Singleton. Quindi, oltre al fatto che Singleton renderà il tuo codice strettamente accoppiato (risolvibile da un modello di strategia ... noto anche come Iniezione di dipendenza), ti impedirà anche di chiudere sezioni del codice dalla revisione (librerie condivise).

Quindi anche gli esempi di logger o pool di thread non sono validi e devono essere sostituiti da Strategie.


Gli stessi logger non dovrebbero essere singoli. Il sistema generale di messaggistica "broadcast" dovrebbe essere. I logger stessi sono abbonati ai messaggi di trasmissione.
CashCow

Anche i pool di thread non dovrebbero essere singoli. La domanda generale è vorresti mai più di uno di loro? Sì. L'ultima volta che li ho usati avevamo 3 diversi pool di thread in una sola applicazione.
CashCow

3

La maggior parte delle persone usa i singoli quando stanno cercando di sentirsi a proprio agio nell'usare una variabile globale. Ci sono usi legittimi, ma la maggior parte delle volte quando le persone li usano, il fatto che ci possa essere solo un'istanza è solo un fatto banale rispetto al fatto che è accessibile a livello globale.


3

Poiché un singleton consente di creare solo un'istanza, controlla efficacemente la replica dell'istanza. per esempio non avresti bisogno di più istanze di una ricerca, ad esempio una mappa di ricerca morse, quindi avvolgendola in una classe singleton è adatta. E solo perché hai una singola istanza della classe non significa che sei anche limitato sul numero di riferimenti a quell'istanza. È possibile mettere in coda le chiamate (per evitare problemi di threading) all'istanza ed effettuare le modifiche necessarie. Sì, la forma generale di un singleton è pubblica a livello globale, puoi sicuramente modificare il design per creare un singleton più limitato all'accesso. Non l'ho mai stancato prima, ma sono sicuro che sia possibile. E per tutti coloro che hanno commentato dicendo che il modello singleton è assolutamente malvagio, dovresti sapere questo:



2

Di seguito è riportato l'approccio migliore per l'implementazione di un modello singleton thread-safe con deallocazione della memoria nel distruttore stesso. Ma penso che il distruttore dovrebbe essere un optional perché l'istanza singleton verrà automaticamente distrutta al termine del programma:

#include<iostream>
#include<mutex>

using namespace std;
std::mutex mtx;

class MySingleton{
private:
    static MySingleton * singletonInstance;
    MySingleton();
    ~MySingleton();
public:
    static MySingleton* GetInstance();
    MySingleton(const MySingleton&) = delete;
    const MySingleton& operator=(const MySingleton&) = delete;
    MySingleton(MySingleton&& other) noexcept = delete;
    MySingleton& operator=(MySingleton&& other) noexcept = delete;
};

MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
    delete singletonInstance;
};

MySingleton* MySingleton::GetInstance(){
    if (singletonInstance == NULL){
        std::lock_guard<std::mutex> lock(mtx);
        if (singletonInstance == NULL)
            singletonInstance = new MySingleton();
    }
    return singletonInstance;
}

Per quanto riguarda le situazioni in cui è necessario utilizzare le classi singleton, può essere- Se vogliamo mantenere lo stato dell'istanza durante l'esecuzione del programma Se siamo coinvolti nella scrittura nel registro di esecuzione di un'applicazione in cui è necessario solo un'istanza del file essere usato .... e così via. Sarà apprezzabile se qualcuno può suggerire l'ottimizzazione nel mio codice sopra.


2
Questo non è sicuramente migliore. 1: non si sta definendo la semantica della proprietà utilizzando il puntatore. Non dovresti mai usare i puntatori in C ++ a meno che non ti sia preparato a gestirli. 2: L'uso del doppio controllo controllato è antiquato e ci sono modi molto migliori moderni per farlo. 3: I tuoi commenti sulla distruzione sono ingenui. Il recupero della memoria non è il punto di utilizzo del distruttore ma riguarda la pulizia. Suggerimenti per una versione migliore: guarda la domanda. La versione presentata è già molto meglio.
Martin York,

1

Uso Singletons come test di intervista.

Quando chiedo a uno sviluppatore di nominare alcuni modelli di progettazione, se tutto ciò che possono nominare è Singleton, non vengono assunti.


45
Le regole rigide e veloci sull'assunzione ti faranno perdere un'ampia varietà di potenziali dipendenti.
Karl,

13
Esiste un'ampia varietà di idioti. Ciò non significa che dovrebbero essere considerati per l'assunzione. Se qualcuno non può menzionare alcun motivo di progettazione, penso che sarebbe preferibile a qualcuno che conosce il singleton e nessun altro motivo.
jalf

3
Per il libro dei record, la mia risposta è stata ironica. Nel mio vero processo di intervista, provo a valutare se avremo bisogno di tutorare qualcuno in C ++ e quanto sarà difficile. Alcuni dei miei candidati preferiti sono persone che NON conoscono il C ++ dentro e fuori, ma sono stato in grado di avere un'ottima conversazione con loro al riguardo.
Matt Cruikshank,

4
Voto negativo. In base alla mia esperienza personale, il programmatore potrebbe non essere in grado di nominare altri modelli tranne Singleton, ma ciò non significa che usi Singleton. Personalmente, stavo usando i singleton nel mio codice PRIMA di averne mai sentito parlare (li ho chiamati "globi più intelligenti" - sapevo qual è il globale). Quando ho saputo di loro, quando ho saputo dei loro pro e contro - ho smesso di usarli. Improvvisamente, i test unitari sono diventati molto più interessanti per me quando mi sono fermato ... Questo mi ha reso un programmatore peggiore? Pfff ...
Paulius,

3
Sto anche votando per la domanda senza senso "nomina alcuni modelli di design". Progettare significa capire come applicare i modelli di progettazione, non solo essere in grado di sradicare i loro nomi. Ok, ciò potrebbe non giustificare un downvote ma questa risposta è troll-ish.
CashCow

0

Anti-Uso:

Un grave problema con un uso eccessivo di singleton è che il modello impedisce una facile estensione e scambio di implementazioni alternative. Il nome della classe è hard coded ovunque venga usato il singleton.


Sottovalutato per 2 motivi: 1. Singleton può utilizzare internamente istanze polimorfiche (ad esempio, il Logger globale utilizza strategie polimorfiche di targeting) 2. Può esserci typedef per il nome singleton, quindi il codice dipende fattivamente da typedef.
topright gamedev,

Ho finito per costruire la mia versione di un singleton per essere estendibile usando il modello di modello curiosamente ricorrente.
Zachary Kraus,

0

Penso che questa sia la versione più robusta per C #:

using System;
using System.Collections;
using System.Threading;

namespace DoFactory.GangOfFour.Singleton.RealWorld
{

  // MainApp test application

  class MainApp
  {
    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Same instance?
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 server requests
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // "Singleton"

  class LoadBalancer
  {
    private static LoadBalancer instance;
    private ArrayList servers = new ArrayList();

    private Random random = new Random();

    // Lock synchronization object
    private static object syncLock = new object();

    // Constructor (protected)
    protected LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      // Support multithreaded applications through
      // 'Double checked locking' pattern which (once
      // the instance exists) avoids locking each
      // time the method is invoked
      if (instance == null)
      {
        lock (syncLock)
        {
          if (instance == null)
          {
            instance = new LoadBalancer();
          }
        }
      }

      return instance;
    }

    // Simple, but effective random load balancer

    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Ecco la versione ottimizzata per .NET :

using System;
using System.Collections;

namespace DoFactory.GangOfFour.Singleton.NETOptimized
{

  // MainApp test application

  class MainApp
  {

    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Confirm these are the same instance
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 requests for a server
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // Singleton

  sealed class LoadBalancer
  {
    // Static members are lazily initialized.
    // .NET guarantees thread safety for static initialization
    private static readonly LoadBalancer instance =
      new LoadBalancer();

    private ArrayList servers = new ArrayList();
    private Random random = new Random();

    // Note: constructor is private.
    private LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      return instance;
    }

    // Simple, but effective load balancer
    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Puoi trovare questo modello su dotfactory.com .


3
È possibile eliminare le parti che non sono specificamente collegate a Singletons per facilitare la lettura del codice.
Martin York,

Inoltre, la tua prima versione non è thread-safe a causa del possibile riordino in lettura / scrittura. Vedere stackoverflow.com/questions/9666/...
Thomas Danecker

5
Uh ... lingua sbagliata? La domanda è abbastanza ovviamente taggata in C ++ .
DevSolar

0

Il modello singleton Meyers funziona abbastanza bene per la maggior parte del tempo e nelle occasioni non paga necessariamente per cercare qualcosa di meglio. Finché il costruttore non lancerà mai e non ci sono dipendenze tra i singoli.

Un singleton è un'implementazione per un oggetto accessibile a livello globale (GAO da ora in poi) sebbene non tutti i GAO siano singleton.

I logger stessi non dovrebbero essere singoli, ma i mezzi per accedere dovrebbero idealmente essere accessibili a livello globale, per disaccoppiare da dove viene generato il messaggio di registro da dove o come viene registrato.

La valutazione lazy-loading / lazy è un concetto diverso e di solito lo implementa anche singleton. Viene fornito con molti problemi propri, in particolare la sicurezza dei thread e problemi se fallisce con eccezioni tali che quella che sembrava una buona idea in quel momento risulta non essere poi così eccezionale. (Un po 'come l'implementazione di COW nelle stringhe).

Tenendo presente ciò, i GOA possono essere inizializzati in questo modo:

namespace {

T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;

}

int main( int argc, char* argv[])
{
   T1 t1(args1);
   T2 t2(args2);
   T3 t3(args3);
   T4 t4(args4);

   pt1 = &t1;
   pt2 = &t2;
   pt3 = &t3;
   pt4 = &t4;

   dostuff();

}

T1& getT1()
{
   return *pt1;
}

T2& getT2()
{
   return *pt2;
}

T3& getT3()
{
  return *pt3;
}

T4& getT4()
{
  return *pt4;
}

Non ha bisogno di essere fatto in modo così rozzo, e chiaramente in una libreria caricata che contiene oggetti, probabilmente vorrai qualche altro meccanismo per gestirne la durata. (Inseriscili in un oggetto che ottieni quando carichi la libreria).

Per quanto riguarda quando uso i single? Li ho usati per 2 cose - Una tabella singleton che indica quali librerie sono state caricate con dlopen - Un gestore di messaggi a cui i logger possono iscriversi e al quale è possibile inviare messaggi. Richiesto specificamente per i gestori di segnali.


0

Ancora non capisco perché un singleton debba essere globale.

Stavo per produrre un singleton in cui nascondevo un database all'interno della classe come variabile statica costante privata e creavo funzioni di classe che utilizzano il database senza mai esporlo all'utente.

Non vedo perché questa funzionalità sia negativa.


Non capisco perché pensi che debba essere globale.
Martin York,

secondo questo thread, tutti dicevano che un singleton deve essere globale
Zachary Kraus,

1
No. Il thread indica che un singelton ha uno stato globale. Non che sia una variabile globale. La soluzione che proponi ha uno stato globale. La soluzione che proponi sta usando anche una variabile globale; un membro statico di una classe è un oggetto di "Durata memoria statica" una variabile globale è un oggetto di "Durata memoria statica". Quindi i due sono sostanzialmente la stessa cosa con semantica / ambiti leggermente diversi.
Martin York,

Quindi una variabile statica privata è ancora globale a causa della "Durata dell'archiviazione statica"?
Zachary Kraus,

1
Nota: ti sei perso il mio bit deliberatamente nessuno dichiarato. Il tuo progetto di usare un membro "privato" statico non è male allo stesso modo di un singelton. Perché non introduce "stato mutabile globale". Ma non è nemmeno un singleton. Un singleton è una classe progettata in modo che possa esistere solo un'istanza dell'oggetto. Quello che stai suggerendo è un singolo stato condiviso per tutti gli oggetti di una classe. Concetto diverso.
Martin York,

0

Li trovo utili quando ho una classe che incapsula molta memoria. Ad esempio, in un recente gioco a cui sto lavorando, ho una classe di mappe di influenza che contiene una raccolta di matrici molto grandi di memoria contigua. Voglio che tutto sia allocato all'avvio, tutto liberato allo spegnimento e ne voglio sicuramente solo una copia. Devo anche accedervi da molti posti. Trovo che lo schema singleton sia molto utile in questo caso.

Sono sicuro che ci sono altre soluzioni ma trovo questa molto utile e facile da implementare.


0

Se sei tu quello che ha creato il singleton e che lo usa, non farlo come singleton (non ha senso perché puoi controllare la singolarità dell'oggetto senza renderlo singleton) ma ha senso quando sei uno sviluppatore di un libreria e vuoi fornire un solo oggetto ai tuoi utenti (in questo caso sei tu che hai creato il singleton, ma non sei l'utente).

I singleton sono oggetti quindi usali come oggetti, molte persone accedono ai singleton direttamente chiamando il metodo che lo restituisce, ma questo è dannoso perché stai facendo il tuo codice sa che l'oggetto è singleton, preferisco usare i singleton come oggetti, li passo tramite il costruttore e li uso come oggetti ordinari, in questo modo il tuo codice non sa se questi oggetti sono singoli o meno e ciò rende le dipendenze più chiare e aiuta un po 'per il refactoring ...


-1

Nelle app desktop (lo so, solo noi dinosauri le scriviamo più!) Sono essenziali per ottenere impostazioni globali relativamente immutabili delle applicazioni: la lingua dell'utente, il percorso per aiutare i file, le preferenze dell'utente, ecc. Che altrimenti dovrebbero propagarsi in ogni classe e ogni finestra di dialogo .

Modifica - ovviamente questi dovrebbero essere di sola lettura!


Ma questo è chiedere la domanda; perché fare la lingua dell'utente e il percorso del file di aiuto devono essere metodo istanze a tutti ?
DrPizza,

2
Abbiamo dei globi per questo. Non c'è bisogno di renderli singoli
jalf

Variabili globali - come li serializzi dal registro / database? Classe Gobal - allora come ti assicuri che ce ne sia solo una?
Martin Beckett,

@mgb: li serializzi leggendo i valori dal registro / database e memorizzandoli nelle variabili globali (questo dovrebbe probabilmente essere fatto nella parte superiore della tua funzione principale). ti assicuri che esiste un solo oggetto di una classe, creando un solo oggetto di classe ... davvero ... è così difficile "grep -rn" new \ + global_class_name "." ? veramente?
Paulius,

7
@mgb: Perché mai dovrei assicurarmene che ce ne sia solo uno? Devo solo sapere che un'istanza rappresenta sempre le impostazioni correnti . ma non c'è motivo per cui non dovrei avere altri oggetti di impostazione intorno al luogo. Forse uno per "le impostazioni che l'utente sta attualmente definendo, ma non ha ancora applicato", per esempio. O uno per "la configurazione che l'utente ha salvato in precedenza in modo da poter tornare in seguito". O uno per ciascuno dei tuoi test unitari.
jalf

-1

Un'altra implementazione

class Singleton
{
public:
    static Singleton& Instance()
    {
        // lazy initialize
        if (instance_ == NULL) instance_ = new Singleton();

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};

3
È davvero orribile. Vedi: stackoverflow.com/questions/1008019/c-singleton-design-pattern/… per una migliore inizializzazione pigra e una distruzione deterministica più importante garantita.
Martin York,

Se hai intenzione di utilizzare i puntatori, Instance()dovrebbe restituire un puntatore, non un riferimento. All'interno del vostro .cppfile inizializzare l'istanza di nulla: Singleton* Singleton::instance_ = nullptr;. E Instance()dovrebbe essere attuata come: if (instance_ == nullptr) instance_ = new Singleton(); return instance_;.
Dennis,
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.