Domanda

Modificare:Da un'altra domanda ho fornito una risposta che contiene collegamenti a molte domande/risposte sui singleton: Maggiori informazioni sui singleton qui:

Allora ho letto il thread Single:buon design o una stampella?
E la discussione infuria ancora.

Vedo i Singleton come un Design Pattern (buono e cattivo).

Il problema con Singleton non è il Pattern ma piuttosto gli utenti (scusate tutti).Tutti, compresi i loro padri, pensano di poterne implementare uno correttamente (e dalle numerose interviste che ho fatto, la maggior parte delle persone non può).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 Singleton!).

Quindi le principali domande a cui bisogna rispondere sono:

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

La mia speranza per questo articolo è che possiamo raccogliere insieme in un unico posto (piuttosto che dover cercare su Google e in più siti) una fonte autorevole di quando (e quindi come) utilizzare correttamente un Singleton.Sarebbe inoltre appropriato un elenco di anti-utilizzo e di implementazioni errate comuni che spieghino perché non funzionano e, nel caso di buone implementazioni, i loro punti deboli.


Quindi fai girare la palla:
Alzerò la mano e dirò che questo è quello che uso ma probabilmente ha problemi.
Mi piace il trattamento dell'argomento da parte di "Scott Myers" nei suoi libri "Effective C++"

Buone situazioni per utilizzare i Singleton (non molti):

  • Strutture di registrazione
  • Piscine per il riciclaggio dei fili
/*
 * 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.Raccogliamo insieme alcune critiche e altre implementazioni.
:-)

È stato utile?

Soluzione

Avete tutti torto.Leggi la domanda.Risposta:

Utilizza un Singleton se:

  • È necessario avere uno e 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é tutti gli altri lo stanno facendo (vedi programmatore del culto del carico su Wikipedia)
  • Nei widget dell'interfaccia utente
  • Dovrebbe essere una cache
  • Nelle stringhe
  • Nelle 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 una sola volta
  • Inizializzazione pigra o di sistema?All'altezza delle tue esigenze
  • A volte il sistema operativo o la JVM creano singleton per te (ad es.in Java ogni definizione di classe è un singleton)
  • Fornire un distruttore o capire in qualche modo come smaltire le risorse
  • Usa poca memoria

Altri suggerimenti

I singleton ti danno la possibilità di combinare due tratti negativi in ​​un'unica classe.Questo è sbagliato praticamente in ogni senso.

Un singleton ti dà:

  1. Accesso globale a un oggetto e
  2. Una garanzia che non più di un oggetto di questo tipo potrà mai essere creato

Il numero uno è semplice.I globali sono generalmente cattivi.Non dovremmo mai rendere gli oggetti accessibili a livello globale a meno che noi Veramente bisogno di essa.

Il numero due può sembrare logico, ma pensiamoci.Quando è stata l'ultima volta che hai creato **accidentalmente* un nuovo oggetto invece di fare riferimento a uno esistente?Poiché questo è contrassegnato dal tag C++, utilizziamo un esempio tratto da quel linguaggio.Scrivi spesso per errore

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

Quando avevi intenzione di scrivere

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

Ovviamente no.Non abbiamo bisogno di protezione contro questo errore, perché questo tipo di errore semplicemente non si verifica.In tal caso, la risposta corretta è tornare a casa e dormire per 12-20 ore sperando di sentirsi meglio.

Se è necessario un solo oggetto, crea semplicemente un'istanza.Se un oggetto deve essere accessibile a livello globale, rendilo globale.Ma ciò non significa che dovrebbe essere impossibile crearne altre istanze.

Il vincolo "è possibile una sola istanza" non ci protegge realmente da probabili bug.Ma ciò fa rendono molto difficile il refactoring e la manutenzione del nostro codice.Perché molto spesso lo scopriamo Dopo che avevamo bisogno di più di un'istanza.Noi Fare abbiamo più di un database, noi Fare abbiamo più di un oggetto di configurazione, vogliamo diversi logger.I nostri test unitari potrebbero voler essere in grado di creare e ricreare questi oggetti a ogni test, per fare un esempio comune.

Quindi un singleton dovrebbe essere utilizzato se e solo se ne abbiamo bisogno Entrambi le caratteristiche che offre:Se noi Bisogno accesso globale (cosa rara, perché i globali sono generalmente scoraggiati) E Noi Bisogno per impedire a chiunque di mai creare più di un'istanza di una classe (che mi sembra un problema di progettazione).L'unico motivo che riesco a capire è se la creazione di due istanze corromperebbe lo stato dell'applicazione, probabilmente perché la classe contiene un numero di membri statici o sciocchezze simili.In tal caso la risposta ovvia è correggere 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 possono essere create.

Se hai assolutamente bisogno di limitare il numero di istanze di una classe a una sola e non è possibile gestire in modo sicuro la creazione di una seconda istanza, applicala.Ma non renderlo accessibile anche a livello globale.

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

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

1) I singleton forniscono un meccanismo di accesso globale a un oggetto.Anche se potrebbero essere leggermente più thread-safe o leggermente più affidabili nei linguaggi senza un ordine di inizializzazione ben definito, questo utilizzo è ancora l'equivalente morale di una variabile globale.È una variabile globale decorata con una sintassi scomoda (foo::get_instance() invece di g_foo, diciamo), ma ha esattamente lo stesso scopo (un singolo oggetto accessibile attraverso l'intero programma) e presenta esattamente gli stessi inconvenienti.

2) I singleton impediscono più istanziazioni di una classe.È raro, IME, che questo tipo di funzionalità venga integrato in una classe.Normalmente è una cosa molto più contestuale;molte delle cose che sono considerate una e una sola in realtà sono solo una.IMO una soluzione più appropriata è creare solo una sola istanza, finché non ti rendi conto che hai bisogno di più di un'istanza.

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

Singleton può essere sgradevole quando è necessario test il codice.Generalmente 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.

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

Potrebbe farlo monitoraggio delle dipendenze Più forte.Quando tutto dipende dal tuo Singleton, è più difficile cambiarlo, dividerlo in due, ecc.Generalmente sei bloccato con esso.Ciò ostacola anche la flessibilità.Investigarne alcuni Iniezione di dipendenza quadro per cercare di alleviare questo problema.

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

Java in particolare utilizza i singleton in sostituzione delle variabili globali, poiché tutto deve essere contenuto in una classe.Le variabili più vicine alle variabili globali sono le variabili statiche pubbliche, che possono essere utilizzate come se fossero globali import static

Il C++ ha variabili globali, ma l'ordine in cui vengono richiamati 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 utilizzano molto poco i singleton perché è possibile utilizzare invece variabili globali all'interno di un modulo.

Quindi quando è buono/cattivo usare un singleton?Più o meno esattamente quando sarebbe positivo/negativo utilizzare una variabile globale.

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

Per il mio valore di 2p, penso che sia importante avere una durata definita per i tuoi singleton (quando è assolutamente necessario usarli).Normalmente non permetto l'elettricità statica get() la funzione istanzia qualsiasi cosa e lascia l'installazione e la distruzione ad alcune sezioni dedicate dell'applicazione principale.Ciò aiuta a evidenziare le dipendenze tra i singleton, ma, come sottolineato in precedenza, è meglio evitarle se possibile.

  • Come implementare correttamente un Singleton

C'è un problema che non ho mai visto menzionato, qualcosa che ho riscontrato in un lavoro precedente.Avevamo singleton C++ condivisi tra DLL e i soliti meccanismi per garantire che una singola istanza di una classe semplicemente non funzionasse.Il problema è che ogni DLL ottiene il proprio set di variabili statiche, insieme all'EXE.Se la tua funzione get_instance è in linea o parte di una libreria statica, ogni DLL finirà 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 suddividere le istanze.

Il primo esempio non è thread-safe: se due thread chiamano getInstance contemporaneamente, quello statico sarà un PITA.Una qualche forma di mutex aiuterebbe.

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 es.a scopo di test.

Alcuni aspetti utili dei singleton:

  1. istanziazione pigra o anticipata
  2. utile per un oggetto che richiede impostazione e/o stato

Tuttavia, non è necessario utilizzare un singleton per ottenere questi vantaggi.Puoi scrivere un oggetto normale che faccia il lavoro e poi fare in modo che le persone vi accedano tramite una factory (un oggetto separato).La fabbrica può preoccuparsi solo di istanziarne uno, riutilizzarlo, ecc., Se necessario.Inoltre, se si programma su un'interfaccia anziché su una classe concreta, la fabbrica può utilizzare strategie, ad es.puoi attivare e disattivare varie implementazioni dell'interfaccia.

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

I singleton sono utili quando hai molto codice in esecuzione quando inizializzi e ti opponi.Ad esempio, quando usi iBatis quando imposti un oggetto di persistenza, deve leggere tutte le configurazioni, analizzare le mappe, assicurarsi che sia tutto corretto, ecc.prima di arrivare al tuo 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 dei Singleton è che interrompono l'eredità.Non puoi derivare una nuova classe per offrirti funzionalità estese a meno che tu non abbia accesso al codice in cui viene fatto riferimento al Singleton.Quindi, al di là del fatto che Singleton renderà il tuo codice strettamente accoppiato (risolvibile da un modello di strategia ...alias Dependency Injection) 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 cerca di sentirsi a proprio agio nell'usare una variabile globale.Esistono usi legittimi, ma nella maggior parte dei casi, quando le persone li utilizzano, il fatto che possa essercene una sola istanza è solo un fatto banale rispetto al fatto che sia accessibile a livello globale.

Poiché un singleton consente la creazione di una sola istanza, controlla efficacemente la replica dell'istanza.ad esempio non avresti bisogno di più istanze di una ricerca: ad esempio una mappa di ricerca morse, quindi racchiuderla in una classe singleton è adatta.E solo perché hai una singola istanza della classe non significa che sei limitato anche al numero di riferimenti a quell'istanza.È possibile accodare 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 certamente modificare il design per creare un singleton con accesso più limitato.Non mi sono mai stancato di questo prima, ma sono sicuro che è possibile.E a tutti coloro che hanno commentato dicendo che il modello singleton è assolutamente malvagio dovreste sapere questo:sì, è un male se non lo usi correttamente o entro i limiti di funzionalità effettiva e comportamento prevedibile:non GENERALIZZARE.

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

Utilizzo Singleton come test di intervista.

Quando chiedo a uno sviluppatore di nominare alcuni modelli di progettazione, se tutto ciò che riesce a nominare è Singleton, non viene assunto.

Di seguito è riportato l'approccio migliore per implementare 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<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 dobbiamo utilizzare le classi singleton possono 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 possa suggerire l'ottimizzazione nel mio codice sopra.

Anti-utilizzo:

Uno dei problemi principali con l'utilizzo eccessivo di singleton è che il modello impedisce una facile estensione e scambio di implementazioni alternative.Il nome della classe è hardcoded ovunque venga utilizzato il singleton.

Penso che questo sia il 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 il 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 di Meyers funziona abbastanza bene per la maggior parte del tempo e, in quelle occasioni, non è necessariamente conveniente cercare qualcosa di meglio.Finché il costruttore non lancia mai eccezioni e non ci sono dipendenze tra i singleton.

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

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

Il caricamento lento/la valutazione pigra è un concetto diverso e anche il singleton di solito lo implementa.Presenta molti problemi propri, in particolare la sicurezza del thread e problemi se fallisce con eccezioni tali che quella che sembrava una buona idea in quel momento si rivela non essere poi così eccezionale.(Un po' come l'implementazione di COW nelle stringhe).

Tenendo 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 è necessario che sia fatto in modo così rozzo, e chiaramente in una libreria caricata che contiene oggetti probabilmente vorrai che qualche altro meccanismo ne gestisca la durata.(Inseriscili in un oggetto che ottieni quando carichi la libreria).

Per quanto riguarda quando utilizzo i singleton?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 a cui puoi inviare messaggi.Richiesto specificamente per i gestori di segnale.

Ancora non capisco perché un singleton debba essere globale.

Avrei prodotto un singleton in cui avrei nascosto un database all'interno della classe come variabile statica costante privata e creato funzioni di classe che utilizzano il database senza mai esporre il database all'utente.

Non vedo perché questa funzionalità sarebbe negativa.

Li trovo utili quando ho una classe che incapsula molta memoria.Ad esempio, in un gioco recente su cui ho lavorato ho una classe di mappa di influenza che contiene una raccolta di array molto grandi di memoria contigua.Lo voglio tutto allocato all'avvio, tutto liberato allo spegnimento e ne voglio sicuramente solo una copia.Devo anche accedervi da molti posti.Trovo che il pattern singleton sia molto utile in questo caso.

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

Se sei tu quello che ha creato il singleton e chi lo usa, non renderlo 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 ha 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 sapere al tuo codice che l'oggetto è singleton, preferisco usare i singleton come oggetti, li passo attraverso il costruttore e li uso come oggetti ordinari, in questo modo il tuo codice non sa se questi oggetti sono singleton o meno e questo rende le dipendenze più chiare e aiuta un po' per il refactoring...

Nelle app desktop (lo so, solo noi dinosauri le scriviamo ormai!) sono essenziali per ottenere impostazioni globali dell'applicazione relativamente immutabili: la lingua dell'utente, il percorso dei file della guida, le preferenze dell'utente ecc., che altrimenti dovrebbero propagarsi in ogni classe e ogni finestra di dialogo .

Modifica: ovviamente dovrebbero essere di sola lettura!

Un'altra implementazione

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

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top