it-swarm-eu.dev

Singleton: come dovrebbe essere usato

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

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

Vedo i Singletons come un Design Pattern (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, la maggior parte delle persone non può). 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 quindi come) utilizzare correttamente 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:
Solleverò la mano e dirò che è quello che uso ma probabilmente ho problemi.
Mi piace "Scott Myers" nel trattare 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.
:-)

288
Martin York

Tutti voi avete torto. Leggi la domanda Risposta:

Usa un Singleton se:

  • Devi avere 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 stanno facendo tutti gli altri (vedi programmatore cult di cargo in wikipedia)
  • Nei widget dell'interfaccia utente
  • Dovrebbe essere una cache
  • A 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 es. In Java ogni definizione di classe è un singleton)
  • Fornire un distruttore o in qualche modo capire come smaltire le risorse
  • Usa poca memoria
165
Javaxpert

I single ti danno la possibilità di combinare due tratti cattivi 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 a meno che 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 è quella di tornare a casa e dormire per 12-20 ore e sperare che ti senti 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 rende molto difficile il refactoring e la manutenzione del nostro codice. Perché abbastanza spesso scopriamo più tardi che abbiamo bisogno di più di un'istanza. Noi abbiamo abbiamo più di un database, noi abbiamo ne abbiamo più di uno oggetto 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 un singleton dovrebbe essere usato se e solo se, abbiamo bisogno di entrambi i tratti che offre: Se noi abbiamo bisogno di accesso globale (che è raro, perché i globuli sono generalmente scoraggiati) e noi abbiamo bisogno di di impedire a chiunque di sempre creare più di un'istanza di una 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.

71
jalf

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 mascherata da 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 svantaggi.

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 delle cose che sono considerate come una e una sola sono in realtà solo una persona 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.

35
DrPizza

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

Singleton può essere brutto quando devi test 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 un 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 tracciamento delle dipendenze più difficile. Quando tutto dipende dal tuo Singleton, è più difficile cambiarlo, dividerlo in due, ecc. In genere sei bloccato con esso. Ciò ostacola anche la flessibilità. Indagare un quadro Dependency Injection per cercare di alleviare questo problema.

26
Paweł Hajdan

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. Le variabili globali più vicine sono le variabili statiche pubbliche, che possono essere utilizzate come se fossero globali con import 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 buono/cattivo usare una variabile globale.

12
Eli Courtwright
  • 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 parte di una libreria statica, ogni DLL verrà visualizzato con la propria copia del "singleton".

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

6
Mark Ransom

Modern C++ Design 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 lascio che la funzione statica get() crei un'istanza per nulla e lascio il set-up e la distruzione ad alcune sezioni dedicate dell'applicazione principale. Questo aiuta a evidenziare le dipendenze tra i singoli - ma, come sottolineato sopra, è meglio evitarli, se possibile.

6
tenpn

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

5
Rob

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 es. 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.

4
lexh

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 avvolgendolo in una classe singleton è appropriato. 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 certamente modificare il design per creare un singleton più limitato all'accesso. Non l'ho mai stancato prima, ma sono sicuro che sia possibile. E a tutti coloro che hanno commentato dicendo che il modello singleton è assolutamente malvagio, dovresti saperlo: sì, è malvagio se non lo usi correttamente o al suo interno confina con funzionalità effettiva e comportamento prevedibile: non GENERALIZZARE.

3
gogole

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, deve 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.

3
Brian

La maggior parte delle persone usa i singoli quando stanno cercando di sentirsi bene 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
Brad Barker

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 ... aka 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 devono essere sostituiti da Strategie.

3
ZebZiggle

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

2
Matt Cruikshank

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.

1
Matt Cruikshank

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.

1
A. Gupta

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 vuoi 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
CashCow

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 ...

0
La VloZ Merrill

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.

0
Adam Franco

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

Penso che questa sia la versione più solida 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 .

0
artur02

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.

0
Zachary Kraus