it-swarm.it

Quindi i Singleton sono cattivi, e allora?

Ultimamente si è discusso molto dei problemi legati all'uso (e all'uso eccessivo) dei singleton. Sono stato una di quelle persone all'inizio della mia carriera. Riesco a capire qual è il problema adesso, eppure ci sono ancora molti casi in cui non riesco a vedere una valida alternativa - e non molte delle discussioni anti-Singleton ne forniscono davvero una.

Ecco un esempio reale di un grande progetto recente in cui sono stato coinvolto:

L'applicazione era un client denso con molte schermate e componenti separati che utilizza enormi quantità di dati da uno stato del server che non viene aggiornato troppo spesso. Questi dati sono stati sostanzialmente memorizzati nella cache in un oggetto "manager" Singleton - il temuto "stato globale". L'idea era quella di avere questo unico posto nell'app che mantenga i dati archiviati e sincronizzati, e quindi qualsiasi nuova schermata che viene aperta può semplicemente interrogare la maggior parte di ciò di cui hanno bisogno da lì, senza fare richieste ripetitive per vari dati di supporto dal server. La richiesta costante al server richiederebbe troppa larghezza di banda - e sto parlando di migliaia di dollari di fatture Internet extra a settimana, quindi era inaccettabile.

Esiste un altro approccio che potrebbe essere appropriato qui che fondamentalmente avere questo tipo di oggetto cache del gestore dati globale? Naturalmente questo oggetto non deve essere ufficialmente un "Singleton", ma concettualmente ha senso esserlo. Che cos'è un'alternativa pulita a Nizza qui?

570
Bobby Tables

È importante distinguere qui tra singole istanze e Singleton design pattern .

Le singole istanze sono semplicemente una realtà. La maggior parte delle app sono progettate per funzionare solo con una configurazione alla volta, un'interfaccia utente alla volta, un file system alla volta e così via. Se ci sono molti stati o dati da mantenere, allora sicuramente vorresti avere solo un'istanza e tenerlo in vita il più a lungo possibile.

Il modello di progettazione Singleton è un tipo molto specifico di singola istanza, in particolare uno che è:

  • Accessibile tramite un campo di istanza globale e statico;
  • Creato all'inizializzazione del programma o al primo accesso;
  • Nessun costruttore pubblico (impossibile creare un'istanza direttamente);
  • Mai liberato esplicitamente (liberato implicitamente al termine del programma).

È a causa di questa specifica scelta progettuale che il modello introduce diversi potenziali problemi a lungo termine:

  • Incapacità di usare classi astratte o di interfaccia;
  • Incapacità di sottoclasse;
  • Elevato accoppiamento attraverso l'applicazione (difficile da modificare);
  • Difficile da testare (impossibile falsificare/simulare nei test unitari);
  • Difficile parallelizzare in caso di stato mutevole (richiede un blocco esteso);
  • e così via.

Nessuno di questi sintomi è in realtà endemico in singole istanze, solo il modello Singleton.

Cosa puoi fare invece? Semplicemente non usare il modello Singleton.

Citando dalla domanda:

L'idea era quella di avere questo unico posto nell'app che mantenga i dati archiviati e sincronizzati, e quindi qualsiasi nuova schermata che viene aperta può semplicemente interrogare la maggior parte di ciò di cui hanno bisogno da lì, senza fare richieste ripetitive per vari dati di supporto dal server. La richiesta costante al server richiederebbe troppa larghezza di banda - e sto parlando di migliaia di dollari di fatture Internet extra a settimana, quindi era inaccettabile.

Questo concetto ha un nome, come suggerisci, ma sembra incerto. Si chiama cache . Se vuoi divertirti, puoi chiamarlo "cache offline" o solo una copia offline di dati remoti.

Una cache non deve essere un singleton. potrebbe essere necessario essere una singola istanza se si desidera evitare di recuperare gli stessi dati per più istanze della cache; ma ciò non significa che devi effettivamente esporre tutto a tutti .

La prima cosa che farei è separare le diverse aree funzionali della cache in interfacce separate. Ad esempio, supponiamo che stiate realizzando il peggior clone di YouTube al mondo basato su Microsoft Access:

 MSAccessCache 
 ▲ 
 | 
 + ----------------- + -------- --------- + 
 | | | 
 IMediaCache IProfileCache IPageCache 
 | | | 
 | | | 
 VideoPage MyAccountPage MostPopularPage 

Qui hai diverse interfacce che descrivono i specifici tipi di dati a cui una particolare classe potrebbe aver bisogno di accedere - media, profili utente e statici pagine (come la prima pagina). Tutto ciò è implementato da una mega-cache, ma tu progetti le tue singole classi per accettare invece le interfacce, quindi a loro non importa che tipo di istanza abbiano. Inizializzi l'istanza fisica una volta, quando il tuo programma viene avviato, e poi inizi a passare attraverso le istanze (trasmetti a un particolare tipo di interfaccia) tramite costruttori e proprietà pubbliche.

Questo è chiamato Dependency Injection , a proposito; non è necessario utilizzare Spring o alcun contenitore IoC speciale, purché il progetto di classe generale accetti le sue dipendenze dal chiamante invece di un'istanza da soli o riferendosi allo stato globale .

Perché dovresti usare il design basato sull'interfaccia? Tre motivi:

  1. Rende il codice più facile da leggere; puoi capire chiaramente dalle interfacce esattamente da quali dati dipendono le classi dipendenti.

  2. Se e quando ti rendi conto che Microsoft Access non è stata la scelta migliore per un back-end di dati, puoi sostituirlo con qualcosa di meglio - diciamo SQL Server.

  3. Se e quando ti rendi conto che SQL Server non è la scelta migliore per i supporti nello specifico , puoi interrompere l'implementazione senza influire su nessun'altra parte del sistema. È qui che entra in gioco il vero potere dell'astrazione.

Se vuoi fare un passo avanti, puoi usare un contenitore IoC (DI framework) come Spring (Java) o Unity (.NET). Quasi ogni framework DI eseguirà la propria gestione a vita e specificatamente consentirà di definire un particolare servizio come una singola istanza (spesso chiamandolo "singleton", ma questo è solo per familiarità). Fondamentalmente questi framework ti risparmiano la maggior parte del lavoro delle scimmie nel passare manualmente le istanze, ma non sono strettamente necessari. Non hai bisogno di strumenti speciali per implementare questo disegno.

Per completezza, dovrei sottolineare che il progetto sopra non è nemmeno l'ideale. Quando hai a che fare con una cache (come sei), dovresti effettivamente avere un livello completamente separato . In altre parole, un design come questo:

 + - IMediaRepository 
 | 
 Cache (generico) --------------- + - IProfileRepository 
 ▲ | 
 | + - IPageRepository 
 + ----------------- + ----------------- + 
 | | | 
 IMediaCache IProfileCache IPageCache 
 | | | 
 | | | 
 VideoPage MyAccountPage MostPopularPage 

Il vantaggio di questo è che non hai nemmeno bisogno di spezzare la tua istanza Cache se decidi di eseguire il refactoring; puoi cambiare il modo in cui i file multimediali vengono archiviati semplicemente alimentandoli con un'implementazione alternativa di IMediaRepository. Se pensi a come si adatta, vedrai che crea sempre e solo un'istanza fisica di una cache, quindi non dovrai mai recuperare gli stessi dati due volte.

Niente di tutto ciò significa che ogni singolo software al mondo debba essere progettato secondo questi rigorosi standard di alta coesione e accoppiamento lento; dipende dalle dimensioni e dalla portata del progetto, dal tuo team, dal tuo budget, dalle scadenze, ecc. Ma se stai chiedendo quale sia il miglior design (da usare al posto di un singleton), allora è così.

Post scriptum Come altri hanno già detto, probabilmente non è la migliore idea per le classi dipendenti di essere consapevoli che stanno usando una cache - che è un dettaglio di implementazione di cui semplicemente non dovrebbero mai preoccuparsi. Detto questo, l'architettura complessiva sembrerebbe ancora molto simile a quanto sopra, non si farebbe riferimento alle singole interfacce come Caches . Invece li chiameresti Servizi o qualcosa di simile.

826
Aaronaught

Nel caso in cui dai, sembra che l'uso di un Singleton non sia il problema, ma il sintomo di un problema - un problema architettonico più grande.

Perché le schermate interrogano l'oggetto cache per i dati? La memorizzazione nella cache deve essere trasparente per il client. Dovrebbe esserci un'astrazione appropriata per fornire i dati e l'implementazione di tale astrazione potrebbe utilizzare la memorizzazione nella cache.

È probabile che il problema sia che le dipendenze tra parti del sistema non siano impostate correttamente e questo è probabilmente sistemico.

Perché gli schermi devono avere conoscenza di dove ottengono i loro dati? Perché le schermate non vengono fornite con un oggetto in grado di soddisfare le loro richieste di dati (dietro le quali è nascosta una cache)? Spesso la responsabilità per la creazione di schermate non è centralizzata, quindi non vi è alcun chiaro punto di iniezione delle dipendenze.

Ancora una volta, stiamo esaminando problemi di architettura e design su larga scala.

Inoltre, è molto importante capire che durata di un oggetto può essere completamente divorziato dal modo in cui l'oggetto viene trovato uso.

Una cache dovrà vivere per tutta la durata dell'applicazione (per essere utile), quindi la durata dell'oggetto è quella di un Singleton.

Ma il problema con Singleton (almeno l'implementazione comune di Singleton come classe/proprietà statica), è come le altre classi che lo usano cercano di trovarlo.

Con un'implementazione statica di Singleton, la convenzione è semplicemente usarla ovunque sia necessario. Ma ciò nasconde completamente la dipendenza e accoppia strettamente le due classi.

Se forniamo la dipendenza alla classe, tale dipendenza è esplicita e tutta la classe che consuma deve avere conoscenza del contratto disponibile per l'uso.

48
quentin-starin

Ho scritto un intero capitolo proprio su questa domanda. Principalmente nel contesto dei giochi, ma la maggior parte dovrebbe applicarsi al di fuori dei giochi.

tl; dr:

Il modello Gang of Four Singleton fa due cose: offrire un comodo accesso a un oggetto da qualsiasi luogo e assicurarsi che sia possibile crearne solo un'istanza. Il 99% delle volte, tutto ciò che ti interessa è la prima metà, e il trasporto lungo la seconda metà per ottenerlo aggiunge una limitazione non necessaria.

Non solo, ma ci sono soluzioni migliori per un accesso conveniente. Rendere globale un oggetto è l'opzione nucleare per risolverlo e rende facile distruggere l'incapsulamento. Tutto ciò che è negativo sui globali si applica completamente ai singoli.

Se lo stai usando solo perché hai molti posti nel codice che devono toccare lo stesso oggetto, prova a trovare un modo migliore per assegnarlo a solo quegli oggetti senza esporlo a l'intero codebase. Altre soluzioni:

  • Abbandona del tutto. Ho visto molte classi singleton che non hanno alcuno stato e sono solo sacche di funzioni di supporto. Quelli non hanno affatto bisogno di un'istanza. Basta renderle funzioni statiche o spostarle in una delle classi che la funzione accetta come argomento. Non avresti bisogno di una speciale classe Math se solo potessi fare 123.Abs().

  • Passalo in giro. La soluzione semplice se un metodo ha bisogno di qualche altro oggetto è semplicemente passarlo. Non c'è niente di sbagliato nel far passare alcuni oggetti.

  • Inseriscilo nella classe base. Se hai molte classi che hanno tutti bisogno di accedere a qualche oggetto speciale e condividono una classe base, puoi rendere quell'oggetto un membro sulla base. Quando lo costruisci, passa l'oggetto. Ora tutti gli oggetti derivati ​​possono ottenerlo quando ne hanno bisogno. Se lo rendi protetto, assicurati che l'oggetto rimanga incapsulato.

45
munificent

Il problema non è di per sé lo stato globale di per sé.

Davvero devi solo preoccuparti di global mutable state. Lo stato costante non è influenzato da effetti collaterali e quindi è meno problematico.

La principale preoccupazione con singleton è che aggiunge l'accoppiamento e quindi rende le cose più difficili. È possibile ridurre l'accoppiamento ottenendo il singleton da un'altra fonte (ad esempio una fabbrica). Ciò ti consentirà di disaccoppiare il codice da un'istanza particolare (anche se diventi più accoppiato alla fabbrica (ma almeno la fabbrica può avere implementazioni alternative per fasi diverse)).

Nella tua situazione penso che tu possa cavartela finché il tuo singleton implementa effettivamente un'interfaccia (in modo che un'alternativa possa essere usata in altre situazioni).

Ma un altro grande svantaggio dei singleton è che una volta sul posto rimuoverli dal codice e sostituirli con qualcos'altro diventa un vero e proprio compito difficile (c'è di nuovo quell'accoppiamento).

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.
21
Martin York

E allora? Dal momento che nessuno lo ha detto: Toolbox. Cioè se vuoi variabili globali.

L'abuso di Singleton può essere evitato osservando il problema da un'angolazione diversa. Supponiamo che un'applicazione abbia bisogno solo di un'istanza di una classe e l'applicazione la configura all'avvio: Perché la classe stessa dovrebbe essere responsabile di essere un singleton? Sembra abbastanza logico che l'applicazione si assuma questa responsabilità, poiché l'applicazione richiede questo tipo di comportamento. L'applicazione, non il componente, dovrebbe essere il singleton. L'applicazione quindi rende disponibile un'istanza del componente che può essere utilizzata da qualsiasi codice specifico dell'applicazione. Quando un'applicazione utilizza diversi di questi componenti, può aggregarli in quella che abbiamo chiamato una cassetta degli attrezzi.

In parole povere, la casella degli strumenti dell'applicazione è un singleton che è responsabile della configurazione di se stesso o di consentire al meccanismo di avvio dell'applicazione di configurarlo ...

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

Ma indovinate un po? È un singleton!

E cos'è un singleton?

Forse è qui che inizia la confusione.

Per me, il singleton è un oggetto obbligato ad avere una sola istanza e sempre. Puoi accedervi ovunque, in qualsiasi momento, senza bisogno di istanziarlo. Ecco perché è così strettamente correlato a static . Per fare un confronto, static è sostanzialmente la stessa cosa, tranne che non è un'istanza. Non abbiamo bisogno di istanziarlo, non possiamo nemmeno, perché è allocato automagicamente. E questo può e può causare problemi.

Dalla mia esperienza, la semplice sostituzione di static per Singleton ha risolto molti problemi in un progetto di borsa patchwork di medie dimensioni in cui mi trovo. Ciò significa solo che ha un certo utilizzo per progetti mal progettati. Penso che ci sia troppo discussione se schema singleton è tile o no e non posso davvero discutere se è davvero cattivo . Ma ci sono ancora buoni argomenti a favore di singleton rispetto ai metodi statici, in generale .

L'unica cosa di cui sono certo è negativa per i singoli, è quando li usiamo ignorando le buone pratiche. Questo è davvero qualcosa di non così facile da affrontare. Ma le cattive pratiche possono essere applicate a qualsiasi modello. E, lo so, è troppo generico per dire che ... Voglio dire, c'è davvero troppo.

Non fraintendetemi!

In poche parole, proprio come global vars , i singletons dovrebbero ancora essere evitati in ogni momento . Soprattutto perché sono abusati eccessivamente. Ma i var globali non possono essere sempre evitati e dovremmo usarli in quest'ultimo caso.

Ad ogni modo, ci sono molti altri suggerimenti oltre a Toolbox, e proprio come la toolbox, ognuno ha la sua applicazione ...

Altre alternative

  • Il miglior articolo che ho appena letto sui singoli suggerisce Service Locator in alternativa. Per me è fondamentalmente un " Static Toolbox ", se vuoi. In altre parole, rendi il Service Locator un Singleton e avrai una Toolbox. Ciò va contro il suo suggerimento iniziale di evitare il singleton, ovviamente, ma questo è solo per far rispettare il problema del singleton: come viene usato, non lo schema in sé.

  • Altri suggerisci Factory Pattern in alternativa. È stata la prima alternativa che ho sentito da un collega e l'abbiamo rapidamente eliminata per il nostro utilizzo come var globale. Di sicuro ha il suo utilizzo, ma anche i singoli.

Entrambe le alternative sopra sono buone alternative. Ma tutto dipende dal tuo utilizzo.

Ora, insinuare singleton dovrebbe essere evitato a tutti i costi è semplicemente sbagliato ...

  • Aaronaught La risposta suggerisce di non usare mai i singoli , per una serie di motivi. Ma sono tutte ragioni contro il modo in cui è mal utilizzato e abusato, non direttamente contro il modello stesso. Sono d'accordo con tutte le preoccupazioni su questi punti, come posso? Penso solo che sia fuorviante.

Le incapacità (di astrarre o sottoclasse) sono davvero lì, ma allora? Non è pensato per quello. Non esiste incapacità di interfacciarsi, per quanto riguarda posso dirlo . Anche l'accoppiamento alto può essere presente, ma è solo perché viene comunemente utilizzato. Non deve . In effetti, l'accoppiamento in sé non ha nulla a che fare con il modello singleton. Ciò premesso, elimina anche la difficoltà di testare. Per quanto riguarda la difficoltà di parallelizzare, ciò dipende dal linguaggio e dalla piattaforma, quindi, ancora una volta, non è un problema sul modello.

Esempi pratici

Vedo spesso 2 in uso, sia a favore che contro i single. Web cache (il mio caso) e servizio log .

La registrazione, alcuni sosterranno , è un perfetto esempio singleton, perché, e cito:

  • I richiedenti hanno bisogno di un oggetto ben noto a cui inviare richieste per accedere. Ciò significa un punto di accesso globale.
  • Poiché il servizio di registrazione è una singola fonte di eventi a cui possono registrarsi più listener, deve esserci solo un'istanza.
  • Sebbene diverse applicazioni possano accedere a diversi dispositivi di output, il modo in cui registrano i loro ascoltatori è sempre lo stesso. Tutta la personalizzazione viene eseguita tramite gli ascoltatori. I clienti possono richiedere la registrazione senza sapere come o dove verrà registrato il testo. Pertanto, ogni applicazione utilizzerà il servizio di registrazione esattamente allo stesso modo.
  • Qualsiasi applicazione dovrebbe essere in grado di cavarsela con una sola istanza del servizio di registrazione.
  • Qualsiasi oggetto può essere un richiedente registrazione, compresi i componenti riutilizzabili, in modo che non debbano essere accoppiati a nessuna particolare applicazione.

Mentre altri sosterranno che è difficile espandere il servizio di registro una volta che ti rendi conto che in realtà non dovrebbe essere solo un'istanza.

Bene, dico che entrambi gli argomenti sono validi. Il problema qui, di nuovo, non è sul modello singleton. È sulle decisioni architettoniche e sulla ponderazione se il refactoring è un rischio praticabile. È un ulteriore problema quando, di solito, il refactoring è l'ultima misura correttiva necessaria.

20
cregox

Il mio problema principale con il modello di progettazione singleton è che è molto difficile scrivere buoni test unitari per la tua applicazione.

Ogni componente che ha una dipendenza da questo "gestore" lo fa interrogando la sua istanza singleton. E se vuoi scrivere un test unitario per un componente del genere, devi inserire i dati in questa istanza singleton, il che potrebbe non essere facile.

Se invece il tuo "manager" viene iniettato nei componenti dipendenti attraverso un parametro costruttore, e il componente non conosce il tipo concreto del gestore, solo un'interfaccia o una classe base astratta implementata dal gestore, quindi un'unità test potrebbe fornire implementazioni alternative del gestore durante il test delle dipendenze.

Se si utilizza IOC per configurare e creare un'istanza dei componenti che compongono l'applicazione, è possibile configurare facilmente il proprio IOC per creare solo un'istanza del "manager", che consente di ottenere lo stesso, solo un'istanza che controlla la cache dell'applicazione globale.

Ma se non ti interessano i test unitari, un modello di progettazione singleton può andare perfettamente bene. (ma non lo farei comunque)

5
Pete

Un singleton non è in un modo fondamentale cattivo, nel senso che tutto il design computing può essere buono o cattivo. Può sempre e solo essere corretto (fornisce i risultati previsti) oppure no. Può anche essere utile o meno, se rende il codice più chiaro o più efficiente.

Un caso in cui i singoli sono utili è quando rappresentano un'entità che è davvero unica. Nella maggior parte degli ambienti, i database sono unici, esiste davvero solo un database. La connessione a quel database può essere complicata perché richiede autorizzazioni speciali o l'attraversamento di diversi tipi di connessione. Organizzare quella connessione in un singleton probabilmente ha molto senso solo per questo motivo.

Ma devi anche essere sicuro che il singleton sia davvero un singleton e non una variabile globale. Ciò è importante quando il singolo database unico è in realtà 4 database, uno ciascuno per dispositivi di produzione, stadiazione, sviluppo e test. Un Database Singleton capirà a quali di esse dovrebbe connettersi, afferrare la singola istanza per quel database, collegarlo se necessario e restituirlo al chiamante.

Quando un singleton non è in realtà un singleton (questo è quando la maggior parte dei programmatori si arrabbiano), è un globale pigramente istanziato, non c'è possibilità di iniettare un'istanza corretta.

Un'altra caratteristica utile di un modello singleton ben progettato è che spesso non è osservabile. Il chiamante chiede una connessione. Il servizio che lo fornisce può restituire un oggetto in pool o, se sta eseguendo un test, può crearne uno nuovo per ogni chiamante o fornire invece un oggetto simulato.

L'uso del modello singleton che rappresenta oggetti reali è perfettamente accettabile. Scrivo per iPhone e ci sono molti singoli nel framework Cocoa Touch. L'applicazione stessa è rappresentata da un singleton della classe UIApplication. Esiste una sola applicazione, quindi è opportuno rappresentarla con un singleton.

L'uso di un singleton come classe di gestione dei dati va bene purché sia ​​progettato correttamente. Se si tratta di un bucket di proprietà dei dati, non è meglio dell'ambito globale. Se è un insieme di getter e setter, è meglio, ma non ancora eccezionale. Se è una classe che gestisce davvero tutte le interfacce con i dati, incluso forse il recupero di dati remoti, la memorizzazione nella cache, l'installazione e lo smontaggio ... Potrebbe essere molto utile.

3
Dan Ray

I singoli sono solo la proiezione di un'architettura orientata ai servizi in un programma.

Un'API è un esempio di singleton a livello di protocollo. Si accede a Twitter, Google ecc. Attraverso quelli che sono essenzialmente singoli. Allora perché i singoli diventano cattivi all'interno di un programma?

Dipende da come pensi a un programma. Se pensi a un programma come a una società di servizi piuttosto che a istanze memorizzate nella cache casualmente, i singoli hanno perfettamente senso.

I singoli sono un punto di accesso al servizio. L'interfaccia pubblica a una libreria di funzionalità strettamente legata che nasconde forse un'architettura interna molto sofisticata.

Quindi non vedo un singleton così diverso da una fabbrica. Il singleton può avere parametri di costruzione passati. Può essere creato da un contesto che sa come risolvere la stampante predefinita contro tutti i possibili meccanismi di selezione, ad esempio. Per i test puoi inserire il tuo finto. Quindi può essere abbastanza flessibile.

La chiave è internamente in un programma quando eseguo e ho bisogno di un po 'di funzionalità posso accedere al singleton con piena fiducia che il servizio è attivo e pronto per l'uso. Questa è la chiave quando ci sono diversi thread che iniziano in un processo che deve passare attraverso una macchina a stati per essere considerato pronto.

In genere avvolgerei una classe XxxService che avvolge un singleton attorno alla classe Xxx. Il singleton non è affatto nella classe Xxx, è separato in un'altra classe, XxxService. Questo perché Xxx può avere più istanze, anche se non è probabile, ma vogliamo comunque avere un'istanza Xxx accessibile globalmente su ciascun sistema. XxxService fornisce una bella separazione delle preoccupazioni. Xxx non deve imporre una politica singleton, tuttavia possiamo usare Xxx come singleton quando ne abbiamo bisogno.

Qualcosa di simile a:

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  
3
Todd Hoff

Chiedi a ogni schermata di accedere a Manager nel loro costruttore.

Quando avvii la tua app, crei un'istanza del gestore e la passi in giro.

Questo si chiama Inversion of Control e consente di sostituire il controller in caso di modifiche alla configurazione e nei test. Inoltre, puoi eseguire diverse istanze dell'applicazione o parti dell'applicazione in parallelo (buono per il test!). Infine il tuo manager morirà con il suo oggetto proprietario (la classe di avvio).

Quindi struttura la tua app come un albero, dove le cose sopra possiedono tutto ciò che viene usato sotto di esse. Non implementare un'app come una mesh, dove tutti conoscono tutti e si trovano l'un l'altro attraverso metodi globali.

1

IMO, il tuo esempio suona bene. Suggerirei il factoring come segue: oggetto cache per ciascun oggetto dati (e dietro ciascuno); gli oggetti cache e gli oggetti db accessor hanno la stessa interfaccia. Questo dà la possibilità di scambiare le cache dentro e fuori il codice; inoltre offre un facile percorso di espansione.

Grafico:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

L'accessorio DB e la cache possono ereditare dallo stesso oggetto o tipo duck in assomigliare allo stesso oggetto, qualunque cosa. Finché è possibile collegare/compilare/test e funziona ancora.

Questo disaccoppia le cose in modo da poter aggiungere nuove cache senza dover accedere e modificare alcuni oggetti Uber-Cache. YMMV. IANAL. ECCETERA.

1
Paul Nathan

Prima domanda, trovi molti bug nell'applicazione? forse dimenticando di aggiornare la cache o cache errata o trovare difficile cambiare? (Ricordo che un'app non cambierebbe le dimensioni se non cambiassi anche il colore ... puoi comunque cambiare il colore indietro e mantenere le dimensioni).

Quello che faresti è avere quella classe ma RIMUOVI TUTTI I MEMBRI STATICI. Ok questo non è nessacary ma lo consiglio. Davvero basta inizializzare la classe come una classe normale e PASSARE il puntatore. Non dire frigen ClassIWant.APtr (). LetMeChange.ANYTHINGATALL () .andhave_no_structure ()

È più lavoro ma in realtà, è meno confuso. Alcuni luoghi in cui non dovresti cambiare cose che ora non puoi, poiché non sono più globali. Tutte le mie lezioni manager sono classi regolari, trattala come quella.

1
user2528

Un po 'tardi alla festa, ma comunque.

Singleton è uno strumento in una cassetta degli attrezzi, proprio come qualsiasi altra cosa. Spero che tu abbia più nella tua cassetta degli attrezzi di un solo martello.

Considera questo:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

vs

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

Il primo caso porta ad un alto accoppiamento ecc .; Il 2 ° modo non ha problemi che @Aaronaught sta descrivendo, per quanto ne so. È tutto su come lo usi.

1
Evgeni