Singleton: come dovrebbe essere usato

Modifica: da un’altra domanda ho fornito una risposta che ha collegamenti a molte domande / risposte sui singleton: Ulteriori informazioni sui singleton qui:

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

Vedo i singleton come un motivo di progettazione (buono e cattivo).

Il problema con Singleton non è il Pattern ma piuttosto gli utenti (mi dispiace per tutti). Tutti e il loro padre pensano di poterne implementare uno correttamente (e dalle molte interviste che ho fatto, molte persone non possono farlo). Anche perché 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 che devono essere risolte sono:

  • Quando dovresti usare un Singleton
  • Come implementate correttamente un Singleton

La mia speranza per questo articolo è che possiamo raccogliere insieme in un unico luogo (piuttosto che dover cercare su Google più siti) una fonte autorevole di quando (e poi come) usare correttamente Singleton. Sarebbe anche appropriato un elenco di Anti-Usage e di comuni cattive implementazioni che spiegassero perché non funzionano e per le buone implementazioni le loro debolezze.


Quindi fai rotolare la palla:
Solleverò la mano e dirò che è quello che uso ma probabilmente ha dei problemi.
Mi piace la gestione “Scott Myers” del sobject nei suoi libri “Effective C ++”

Buone situazioni per usare Singletons (non molti):

  • Strutture 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 critiche e altre implementazioni insieme.
🙂

Tutti voi avete torto. Leggi la domanda. Risposta:

Utilizzare un Singleton se:

  • È necessario avere un solo object di un tipo nel sistema

Non utilizzare un Singleton se:

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

Come creare il miglior singleton:

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

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

Un singleton ti dà:

  1. Accesso globale a un object e
  2. Una garanzia che non si possa mai creare più di un object di questo tipo

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

Il numero due può sembrare sensato, ma pensiamoci. Quando è stata l’ultima volta che ** hai * accidentalmente * creato un nuovo object invece di fare riferimento a uno esistente? Dato che questo è codificato in C ++, usiamo un esempio da quella lingua. Scrive spesso per sbaglio

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

Quando hai intenzione di scrivere

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

Ovviamente no. Non abbiamo bisogno di protezione contro questo errore, perché questo tipo di errore non accade. Se lo fa, la risposta corretta è di andare a casa e dormire per 12-20 ore e spero che tu ti senta meglio.

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

Il vincolo "solo una istanza è ansible" in realtà non ci protegge dai probabili bug. Ma rende il nostro codice molto difficile da refactoring e mantenere. Perché molto spesso scopriamo in seguito che avevamo bisogno di più di un'istanza. Abbiamo più di un database, abbiamo più di un object di configurazione, vogliamo diversi logger. I nostri test unitari potrebbero voler essere in grado di creare e ricreare questi oggetti ogni test, per fare un esempio comune.

Quindi si dovrebbe usare un singleton se e solo se, abbiamo bisogno di entrambi i tratti che offre: se abbiamo bisogno di un accesso globale (cosa rara, perché i globali sono generalmente scoraggiati) e dobbiamo impedire a chiunque di creare più di un'istanza di un class (che mi sembra un problema di design). L'unica ragione che posso vedere per questo è se la creazione di due istanze possa alterare il nostro stato di applicazione, probabilmente perché la class contiene un numero di membri statici o simile stupidità. In tal caso, la risposta ovvia è quella di fissare quella class. Non dovrebbe dipendere dall'essere l'unica istanza.

Se hai bisogno di un accesso globale a un object, rendilo globale, come std::cout . Ma non limitare il numero di istanze che possono essere create.

Se si ha assolutamente bisogno di vincolare il numero di istanze di una class a una sola, e non c'è modo che la creazione di una seconda istanza possa mai essere gestita in sicurezza, quindi applicarla. Ma non renderlo globalmente accessibile pure.

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

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

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

2) Singletons impediscono più istanze di una class. È raro, IME, che questo tipo di funzionalità debba essere inserito in una class. Normalmente è una cosa molto più contestuale; molte delle cose che sono considerate come l’una-e-solo-uno sono in realtà solo per essere-essere-solo-uno. IMO una soluzione più appropriata è quella di creare solo una istanza – fino a quando ti rendi conto che hai bisogno di più di un’istanza.

Una cosa con i modelli: non generalizzare . Hanno tutti i casi quando sono utili e quando falliscono.

Singleton può essere sgradevole quando devi testare il codice. In genere sei bloccato con un’istanza della class e puoi scegliere tra aprire una porta nel costruttore o un metodo per reimpostare lo stato e così via.

Altro problema è che Singleton in effetti non è altro che una variabile globale travestita. Quando hai troppo stato globale condiviso sul tuo programma, le cose tendono a tornare indietro, lo sappiamo tutti.

Può rendere più difficile il tracciamento delle dipendenze . Quando tutto dipende dal tuo Singleton, è più difficile cambiarlo, dividerlo a due, ecc. In genere sei bloccato con esso. Ciò ostacola anche la flessibilità. Indagare su alcune strutture di Dipendenza per Iniezione per cercare di alleviare questo problema.

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

Java in particolare utilizza i singleton come sostituto delle variabili globali, poiché tutto deve essere contenuto all’interno di una class. Il più vicino alle variabili globali sono le variabili statiche pubbliche, che possono essere utilizzate come se fossero globali con l’ import static

Il C ++ ha variabili globali, ma l’ordine in cui i costruttori delle variabili di class globali sono invocati non è definito. In quanto tale, 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 molto poco i singleton perché puoi usare variabili globali all’interno di un modulo.

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

Modern C ++ Design di Alexandrescu ha un singleton generico thread-safe ed ereditabile.

Per il mio valore di 2 pence, penso che sia importante definire delle vite per i tuoi singleton (quando è assolutamente necessario usarli). Normalmente non permetto alla funzione statica get() istanziare nulla, e di lasciare il set-up e la distruzione ad alcune sezioni dedicate dell’applicazione principale. Ciò aiuta a evidenziare le dipendenze tra i singleton – ma, come sottolineato sopra, è meglio evitarli solo se ansible.

  • Come implementate correttamente un Singleton

C’è un problema che non ho mai visto menzionato, qualcosa che ho incontrato in un precedente lavoro. Avevamo singleton in C ++ condivisi tra DLL, e i soliti meccanismi per garantire che una singola istanza di una class semplicemente non funzionasse. Il problema è che ogni DLL ottiene il proprio insieme di variabili statiche, insieme all’EXE. Se la funzione get_instance è inline o parte di una libreria statica, ogni DLL verrà caricata con la propria copia del “singleton”.

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

Il primo esempio non è thread-safe – se due thread chiamano getInstance nello stesso momento, quella statica sarà un PITA. Una qualche forma di mutex sarebbe d’aiuto.

Come altri hanno notato, i principali svantaggi dei singleton includono l’incapacità di estenderli e la perdita del potere di istanziare più di un’istanza, ad esempio per scopi di test.

Alcuni aspetti utili dei singleton:

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

Tuttavia, non è necessario utilizzare un singleton per ottenere questi benefici. Puoi scrivere un object normale che fa il lavoro e poi far sì che le persone accedano tramite una fabbrica (un object separato). La fabbrica può preoccuparsi solo di istanziarne uno e riutilizzarlo, se necessario. Inoltre, se si programma su un’interfaccia anziché su una class concreta, la fabbrica può utilizzare strategie, ovvero è ansible triggersre e distriggersre varie implementazioni dell’interfaccia.

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

I single sono utili quando si esegue un codice lotto quando si inizializza e si esegue l’obiezione. Ad esempio, quando si utilizza iBatis quando si imposta un object di persistenza, è necessario leggere tutte le configurazioni, analizzare le mappe, assicurarsi che siano tutte corrette, ecc. Prima di accedere al 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.

La vera rovina di Singletons è che rompono l’eredità. Non è ansible derivare una nuova class per fornire funzionalità estese a meno che non si abbia accesso al codice in cui si fa riferimento a Singleton. Quindi, al di là del fatto che Singleton renderà il tuo codice strettamente accoppiato (risolvibile da un Pattern di Strategia … ovvero 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 dovrebbero essere sostituiti da Strategie.

La maggior parte delle persone usa i singleton quando cercano di sentirsi bene con l’uso di una variabile globale. Ci sono usi legittimi, ma la maggior parte delle volte in cui le persone li usano, il fatto che ci possa essere solo un’istanza è solo un fatto banale rispetto al fatto che sia accessibile a livello globale.

Poiché un singleton consente solo di creare un’istanza, esso 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 il wrapping in una class singleton è azzeccata. E solo perché hai una singola istanza della class non significa che sei anche limitato sul numero di riferimenti a quell’istanza. È ansible mettere in coda le chiamate (per evitare problemi di threading) all’istanza ed effettuare le modifiche necessarie. Sì, la forma generale di un singleton è globalmente pubblica, puoi certamente modificare il design per creare un singleton più ristretto. Non mi sono mai stancato, ma so che è ansible. E a tutti quelli che hanno commentato dicendo che il modello singleton è completamente malvagio, dovresti saperlo: sì, è malvagio se non lo usi correttamente o entro i limiti di una funzionalità efficace e di un comportamento prevedibile: non generalizzare.

Ma quando ho bisogno di qualcosa come un Singleton, spesso finisco per usare un Counter Schwarz per istanziarlo.

Uso Singletons come test di intervista.

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

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 opzionale perché l’istanza singleton verrà automaticamente distrutta al termine del programma:

 #include #include 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 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 solo una istanza del file deve essere usato …. e così via. Sarà apprezzabile se qualcuno può suggerire l’ottimizzazione nel mio codice sopra.

Anti-Uso:

Uno dei principali problemi con un uso eccessivo di singleton è che il pattern impedisce l’estensione e lo scambio delle implementazioni alternative. Il nome della class è hard coded ovunque sia usato il singleton.

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 .

Il modello singleton Meyers funziona abbastanza bene la maggior parte del tempo, e nelle occasioni in cui lo fa non paga necessariamente per cercare qualcosa di meglio. Finché il costruttore non getterà mai e non ci sono dipendenze tra singleton.

Un singleton è un’implementazione per un object accessibile a livello globale (GAO d’ora in poi) anche se non tutti i GAO sono singleton.

Gli stessi logger non dovrebbero essere singleton, ma i mezzi per registrare dovrebbero essere idealmente accessibili a livello globale, per disaccoppiare dove viene generato il messaggio di log da dove o come viene registrato.

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

Con questo in mente, 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 eseguito in modo così rozzo come quello, e chiaramente in una libreria caricata che contiene oggetti probabilmente vorrà qualche altro meccanismo per gestirne la durata. (Put them in an object that you get when you load the library).

As for when I use singletons? I used them for 2 things – A singleton table that indicates what libraries have been loaded with dlopen – A message handler that loggers can subscribe to and that you can send messages to. Required specifically for signal handlers.

I still don’t get why a singleton has to be global.

I was going to produce a singleton where I hid a database inside the class as a private constant static variable and make class functions that utilize the database without ever exposing the database to the user.

I don’t see why this functionality would be bad.

I find them useful when I have a class that encapsulates a lot of memory. For example in a recent game I’ve been working on I have an influence map class that contains a collection of very large arrays of contiguous memory. I want that all allocated at startup, all freed at shutdown and I definitely want only one copy of it. I also have to access it from many places. I find the singleton pattern to be very useful in this case.

I’m sure there are other solutions but I find this one very useful and easy to implement.

If you are the one who created the singleton and who uses it, dont make it as singleton (it doesn’t have sense because you can control the singularity of the object without making it singleton) but it makes sense when you a developer of a library and you want to supply only one object to your users (in this case you are the who created the singleton, but you aren’t the user).

Singletons are objects so use them as objects, many people accesses to singletons directly through calling the method which returns it, but this is harmful because you are making your code knows that object is singleton, I prefer to use singletons as objects, I pass them through the constructor and I use them as ordinary objects, by that way, your code doesn’t know if these objects are singletons or not and that makes the dependencies more clear and it helps a little for refactoring …

In desktop apps (I know, only us dinosaurs write these anymore!) they are essential for getting relatively unchanging global application settings – the user language, path to help files, user preferences etc which would otherwise have to propogate into every class and every dialog.

Edit – of course these should be read-only !

Another implementation

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