Esistono valide alternative al pattern GOF Singleton?


91

Affrontiamolo. Il Singleton Pattern è un argomento molto controverso con orde di programmatori su entrambi i lati della barriera. Ci sono quelli che pensano che il Singleton non sia altro che una variabile globale glorificata, e altri che giurano per modello e lo usano incessantemente. Tuttavia, non voglio che la controversia Singleton sia al centro della mia domanda. Tutti possono fare un tiro alla fune e combattere e vedere chi vince per quanto mi riguarda . Quello che sto cercando di dire è che non credo ci sia una sola risposta corretta e non sto intenzionalmente cercando di infiammare i litigi partigiani. Sono semplicemente interessato alle alternative singleton quando pongo la domanda:

Ci sono alternative specifiche al pattern GOF Singleton?

Ad esempio, molte volte quando ho utilizzato il pattern singleton in passato, mi interessa semplicemente preservare lo stato / i valori di una o più variabili. Lo stato / i valori delle variabili, tuttavia, possono essere conservati tra ogni istanziazione della classe utilizzando variabili statiche invece di utilizzare il pattern singleton.

Che altra idea hai?

EDIT: Non voglio davvero che questo sia un altro post su "come usare correttamente il singleton". Di nuovo, sto cercando modi per evitarlo. Per divertimento, ok? Immagino di fare una domanda puramente accademica nella tua migliore voce per il trailer di un film, "In un universo parallelo dove non c'è il singleton, cosa potremmo fare?"


Che cosa? Non è né buono né cattivo, ma come posso sostituirlo? Per tutte le persone che dicono che è buono, non partecipare. Tutti voi che dite che è brutto, provatelo mostrandomi come posso vivere senza. Mi sembra polemico.
S.Lott

@CodingWithoutComents: ha letto l'intero post. È così che ho avuto la sensazione di "non rispondere se pensi che i single siano ok".
S.Lott

2
Bene, se è così che è venuto fuori, mi scuso. Pensavo di aver compiuto passi significativi per evitare la polarizzazione. Pensavo di aver posto la domanda in modo che sia gli amanti che gli haters di Singleton potessero entrambi venire via con il fatto che come programmatori tutti abbiamo delle scelte - che il loro non è mai solo un modo corretto
CodingWithoutComments

Se uso Singleton, non ho alcun contributo possibile su come aggirarli. Mi sembra polarizzante.
S.Lott

9
Uso Singleton tutti i giorni, ma questo mi impedisce di pensare che potrebbe esserci un modo migliore per fare le cose? I modelli di design esistono solo da 14 anni. Li prendo come verità biblica? Smettiamo di provare a pensare fuori dagli schemi? Non cerchiamo di far progredire la disciplina di CS?
CodingWithoutComments

Risposte:


76

Alex Miller in " Patterns I Hate " cita quanto segue:

"Quando un singleton sembra la risposta, trovo che spesso sia più saggio:

  1. Crea un'interfaccia e un'implementazione predefinita del tuo singleton
  2. Costruisci una singola istanza della tua implementazione predefinita nella "parte superiore" del tuo sistema. Questo potrebbe essere in una configurazione Spring, o nel codice, o definito in una varietà di modi a seconda del sistema.
  3. Passa la singola istanza in ogni componente che ne ha bisogno (inserimento delle dipendenze)

2
So che ha quasi 5 anni, ma potresti approfondirlo? Penso che mi sia stato insegnato che il modo giusto per creare un singleton fosse con un'interfaccia. Sarei interessato se quello che stavo facendo fosse "OK" e non così terribile come mi è stato detto.
Pureferret

97

Per capire il modo corretto di aggirare i Singleton, devi capire cosa c'è di sbagliato in Singleton (e nello stato globale in generale):

I single nascondono le dipendenze.

Perché è così importante?

Perché se nascondi le dipendenze tendi a perdere di vista la quantità di accoppiamento.

Potresti sostenerlo

void purchaseLaptop(String creditCardNumber, int price){
  CreditCardProcessor.getInstance().debit(creditCardNumber, amount);
  Cart.getInstance().addLaptop();
}

è più semplice di

void purchaseLaptop(CreditCardProcessor creditCardProcessor, Cart cart, 
                    String creditCardNumber, int price){
  creditCardProcessor.debit(creditCardNumber, amount);
  cart.addLaptop();
}

ma almeno la seconda API rende chiaro esattamente quali sono i collaboratori del metodo.

Quindi il modo per aggirare Singletons non è usare variabili statiche o localizzatori di servizi, ma cambiare le classi Singleton in istanze, che vengono istanziate nell'ambito in cui hanno senso e iniettate nei componenti e nei metodi che ne hanno bisogno. Potresti usare un framework IoC per gestirlo, oppure potresti farlo manualmente, ma la cosa importante è sbarazzarti del tuo stato globale e rendere esplicite le dipendenze e le collaborazioni.


+1 Per aver discusso la radice del problema, non solo come aggirarlo.
Phil

9
In un'applicazione multilivello a tutti gli effetti, il secondo metodo crea spesso un'enorme quantità di codice non necessario. Inquinando il codice dei livelli intermedi con la logica da trasferire ai livelli inferiori, oggetti che altrimenti non sarebbero necessari a quel livello, non stiamo creando dipendenze che non devono esserlo? Supponiamo che il livello 4 in uno stack di navigazione avvii un'azione in background, come il caricamento di un file. Ora supponi di voler avvisare l'utente quando termina, ma a quel punto l'utente potrebbe trovarsi in una parte completamente diversa dell'applicazione che condivide solo il livello 1 in comune con la vista di avvio. Tonnellate di codice non necessario ...
Hari Karam Singh

1
@RasmusFaber Un pattern Singleton non nasconde le dipendenze. La tua lamentela riguardo al fatto che nascondere le dipendenze è una preoccupazione separata dal modello. È vero che il pattern rende facile commettere questo errore, ma è assolutamente possibile eseguire insieme IoC e il pattern Singleton in armonia. Ad esempio, posso creare una libreria di terze parti che necessita di una gestione speciale del ciclo di vita delle sue istanze e chiunque utilizzi la mia libreria dovrebbe comunque "passare" un'istanza del mio singleton alle proprie classi utilizzando il classico Dependency Injection anziché "sky hook" il mio singleton da ogni punto di taglio.
Martin Bliss

@HariKaramSingh puoi utilizzare il pattern di iscrizione / pubblicazione o il bus di eventi a livello di sistema (che deve essere condiviso tra tutte le parti interessate, non un singleton) per aggirare questo problema.
Victor Sorokin

4
Anche i pattern pub / sub o osservatore possono essere altrettanto negativi rispetto alla "perdita di traccia dell'accoppiamento" come attesterà chiunque abbia dovuto eseguire il debug attraverso un framework che fa molto affidamento su di essi. Almeno con singleton, il nome del metodo che viene invocato è nello stesso posto del codice di invocazione :) Personalmente, penso che tutte queste strategie abbiano il loro posto e nessuna possa essere implementata ciecamente. I programmatori sciatti faranno spaghetti fuori da qualsiasi schema ei programmatori responsabili non devono caricarsi eccessivamente di limitazioni ideologiche per creare codice elegante.
Hari Karam Singh

14

La soluzione migliore che ho trovato è usare il pattern factory per costruire istanze delle tue classi. Usando il pattern, puoi assicurarti che c'è solo un'istanza di una classe condivisa tra gli oggetti che la usano.

Pensavo che sarebbe stato complicato da gestire, ma dopo aver letto questo post sul blog "Where Have All the Singletons Gone?" , sembra così naturale. E per inciso, aiuta molto a isolare i tuoi unit test.

In sintesi, cosa devi fare? Ogni volta che un oggetto dipende da un altro, ne riceverà un'istanza solo tramite il suo costruttore (nessuna nuova parola chiave nella tua classe).

class NeedyClass {

    private ExSingletonClass exSingleton;

    public NeedyClass(ExSingletonClass exSingleton){
        this.exSingleton = exSingleton;
    }

    // Here goes some code that uses the exSingleton object
}

E poi, la fabbrica.

class FactoryOfNeedy {

    private ExSingletonClass exSingleton;

    public FactoryOfNeedy() {
        this.exSingleton = new ExSingletonClass();
    }

    public NeedyClass buildNeedy() {
        return new NeedyClass(this.exSingleton);
    }
}

Poiché istanzerai la tua factory solo una volta, ci sarà un'unica istanza di exSingleton. Ogni volta che chiami buildNeedy, la nuova istanza di NeedyClass verrà fornita in bundle con exSingleton.

Spero che aiuti. Si prega di segnalare eventuali errori.


5
per assicurarti che la tua Factory sia istanziata solo quando hai bisogno di un costruttore privato e definisci il metodo buildNeedy come metodo statico.
Julien Grenier,

16
Julien ha ragione, questa correzione ha un difetto fondamentale, ovvero che stai implicitamente dicendo che puoi istanziare solo una fabbrica. Se prendi le precauzioni necessarie per assicurarti che venga istanziato solo un factory (come ha detto Julien), ti ritroverai con ... un singleton! In realtà questo approccio aggiunge solo un inutile strato di astrazione in cima al singleton.
Bob Somers

C'è un altro modo per avere una sola istanza. Puoi istanziare la tua fabbrica solo una volta. Non c'è bisogno di imporlo dal compilatore. Questo aiuta anche con le riunioni in cui desideri un'istanza diversa.
venerdì

Questa è la migliore soluzione che abbia mai visto al problema che è stato erroneamente risolto con i singleton: aggiungere dipendenze senza ingombrare ogni chiamata di metodo (e quindi riprogettare l'interfaccia e refactoring delle sue dipendenze). Una leggera modifica è perfetta per la mia situazione in cui non è importante che ci sia solo un'istanza del "singleton", ma è importante che tutti condividano la stessa istanza per impostazione predefinita (la modifica è: invece di usare una factory, usa static metodi per impostare esternamente l'istanza comune e utilizzare tale istanza durante la costruzione).
meustrus

Quindi, in pratica il vantaggio di questo approccio è che basta mischiare intorno un'istanza di oggetto (la fabbrica), piuttosto che potenzialmente un intero gruppo di oggetti (per il quale si sceglie di non utilizzare Singletons)?
Hari Karam Singh

9

Spring o qualsiasi altro contenitore IoC fa un lavoro ragionevolmente buono in questo. Poiché le classi vengono create e gestite all'esterno dell'app stessa, il contenitore può creare semplici singole classi e iniettarle dove necessario.


Hai dei link sugli argomenti di cui sopra?
CodingWithoutComments

Questi framework sembrano specifici per Java: ci sono altre opzioni specifiche per la lingua? O indipendente dalla lingua?
CodingWithoutComments

per .NET: Castle Windsor castleproject.org/container/index.html Microsoft Unity msdn.microsoft.com/en-us/library/cc468366.aspx Unity è in realtà un framework di inserimento delle dipendenze, ma probabilmente funzionerà per questo argomento.
Erik van Brakel

1
perché non ci sono troppi voti per questo? Il CIO è la soluzione perfetta per evitare Singleton.
Sandeep Jindal

@CodingWithoutComments So che PHP ha il Symfony Service Container. I tizi ruby ​​hanno altri modi per iniettare dipendenze però. Hai una lingua specifica che ti interessa?
Bevelopper

9

Non dovresti fare di tutto per evitare qualsiasi schema. L'uso di un modello è una decisione di progettazione o una scelta naturale (va semplicemente a posto). Quando si progetta un sistema, è possibile scegliere se utilizzare un pattern o non utilizzare il pattern. Tuttavia, non dovresti fare di tutto per evitare tutto ciò che in definitiva è una scelta di design.

Non evito il Singleton Pattern. O è appropriato e lo uso o non è appropriato e non lo uso. Credo che sia così semplice.

L'adeguatezza (o la mancanza di ciò) del Singleton dipende dalla situazione. È una decisione progettuale che deve essere presa e le conseguenze di tale decisione devono essere comprese (e documentate).


Stavo per dire la stessa cosa, ma non risponde esattamente alla sua domanda :)
Swati

2
Per favore, parla nella tua risposta quando è appropriato o non appropriato. Abbastanza per favore.
CodingWithoutComments

Risponde alla domanda. Non ho tecniche per evitare l'uso del Singleton Pattern perché non faccio di tutto per evitarlo, e non credo che alcuno sviluppatore dovrebbe farlo.
Thomas Owens

Non credo che tu possa generalizzare quando è o non è appropriato. È tutto specifico del progetto e dipende in gran parte dal design del sistema in questione. Non uso "regole pratiche" per decisioni come questa.
Thomas Owens

Hmm. Non capisco perché questo venga bocciato. Ho risposto alla domanda: quali sono le tue tecniche per evitare l'uso del Singleton Pattern? - dicendo che non ho tecniche perché non faccio di tutto per evitare lo schema. I downvoters potrebbero approfondire il loro ragionamento?
Thomas Owens

5

Monostate (descritto in Agile Software Development di Robert C. Martin) è un'alternativa al singleton. In questo modello i dati della classe sono tutti statici ma i getter / setter non sono statici.

Per esempio:

public class MonoStateExample
{
    private static int x;

    public int getX()
    {
        return x;
    }

    public void setX(int xVal)
    {
        x = xVal;
    }
}

public class MonoDriver
{
    public static void main(String args[])
    {
        MonoStateExample m1 = new MonoStateExample();
        m1.setX(10);

        MonoStateExample m2 = new MonoStateExample();
        if(m1.getX() == m2.getX())
        {
            //singleton behavior
        }
    }
}

Monostate ha un comportamento simile al singleton ma lo fa in un modo in cui il programmatore non è necessariamente consapevole del fatto che viene utilizzato un singleton.


Questo merita più voti; Sono venuto qui da Google alla ricerca di un aggiornamento MonoState!
mahemoff

@Mark Mi sembra che questo design sia molto difficile da bloccare in termini di sicurezza dei thread. Creo un blocco di istanza per ogni variabile statica in MonoStateExample o creo un blocco che tutte le proprietà sfruttano? Entrambi hanno serie ramificazioni che possono essere facilmente risolte eseguite in uno schema Singleton.
Martin Bliss

Questo esempio è un'alternativa al pattern Singleton, ma non risolve i problemi del pattern Singleton.
Sandeep Jindal

4

Il modello singleton esiste perché ci sono situazioni in cui è necessario un singolo oggetto per fornire un insieme di servizi .

Anche se questo è il caso, continuo a considerare l'approccio di creare singleton utilizzando un campo / proprietà statico globale che rappresenta l'istanza, inappropriato. Non è appropriato perché crea una dipendenza nel codice tra il campo statico e l'oggetto no, i servizi forniti dall'oggetto.

Quindi, invece del classico pattern singleton, ti consiglio di utilizzare il pattern "like" del servizio con i contenitori serviti , dove invece di utilizzare il tuo singleton attraverso un campo statico, ottieni un riferimento ad esso tramite un metodo che richiede il tipo di servizio richiesto.

*pseudocode* currentContainer.GetServiceByObjectType(singletonType)
//Under the covers the object might be a singleton, but this is hidden to the consumer.

invece di un singolo globale

*pseudocode* singletonType.Instance

In questo modo, quando vuoi cambiare il tipo di un oggetto da singleton a qualcos'altro, avrai e facile farlo. Inoltre, come ulteriore vantaggio, non è necessario trasferire molte istanze di oggetti a ogni metodo.

Vedi anche Inversion of Control , l'idea è che esponendo i singleton direttamente al consumatore, crei una dipendenza tra il consumatore e l'istanza dell'oggetto, non i servizi dell'oggetto forniti dall'oggetto.

La mia opinione è di nascondere l'uso del pattern singleton ogni volta che è possibile, perché non è sempre possibile evitarlo, o auspicabile.


Non seguo del tutto il tuo esempio di container serviti. Disponi di risorse online a cui collegarti?
CodingWithoutComments

Dai un'occhiata a "Castle Project - Windsor Container" castleproject.org/container/index.html . Purtroppo è molto difficile trovare pubblicazioni astratte su questo argomento.
Pop Catalin

2

Se stai usando un Singleton per rappresentare un singolo oggetto dati, potresti invece passare un oggetto dati come parametro del metodo.

(anche se, direi che questo è il modo sbagliato di usare un Singleton in primo luogo)


1

Se il tuo problema è che vuoi mantenere lo stato, vuoi una classe MumbleManager. Prima di iniziare a lavorare con un sistema, il tuo client crea un MumbleManager, dove Mumble è il nome del sistema. Lo stato viene mantenuto attraverso quello. È probabile che il tuo MumbleManager conterrà una borsa delle proprietà che contiene il tuo stato.

Questo tipo di stile sembra molto simile a C e non molto simile a un oggetto: scoprirai che gli oggetti che definiscono il tuo sistema avranno tutti un riferimento allo stesso MumbleManager.


Senza sostenere o difendere Singleton, devo dire che questa soluzione è un anti-pattern. MumbleManager diventa una "classe di Dio" che contiene tutti i tipi di conoscenze disparate, violando la responsabilità unica. Potrebbe anche essere un singleton per tutte le preoccupazioni dell'applicazione.
Martin Bliss

0

Usa un oggetto semplice e un oggetto di fabbrica. La fabbrica è responsabile del controllo dell'istanza e dei dettagli dell'oggetto semplice solo con le informazioni di configurazione (contiene ad esempio) e il comportamento.


1
Non è spesso una fabbrica singleton? È così che di solito vedo implementati i modelli Factory.
Thomas Owens

0

In realtà, se si progetta da zero evitando i Singeltons, potrebbe non essere necessario aggirare il problema di non utilizzare Singleton utilizzando variabili statiche. Quando si utilizzano variabili statiche, si crea anche più o meno un Singleton, l'unica differenza è che si creano istanze di oggetti diverse, tuttavia internamente si comportano tutti come se utilizzassero un Singleton.

Puoi forse fornire un esempio dettagliato in cui utilizzi un singleton o dove viene attualmente utilizzato un singleton e stai cercando di evitare di usarlo? Questo potrebbe aiutare le persone a trovare una soluzione più elaborata su come la situazione potrebbe essere gestita senza un Singleton.

A proposito, personalmente non ho problemi con i Singleton e non riesco a capire i problemi che le altre persone hanno riguardo ai Singleton. Non vedo niente di male in loro. Cioè, se non li stai abusando. Ogni tecnica utile può essere abusata e, se abusata, porterà a risultati negativi. Un'altra tecnica comunemente usata in modo improprio è l'ereditarietà. Eppure nessuno direbbe che l'eredità sia qualcosa di negativo solo perché alcune persone ne abusano orribilmente.


0

Personalmente per me un modo molto più sensato per implementare qualcosa che si comporta come singleton è usare una classe completamente statica (membri statici, metodi statici, proprietà statiche). Il più delle volte lo implemento in questo modo (non riesco a pensare a differenze di comportamento dal punto di vista dell'utente)


0

Penso che il posto migliore per sorvegliare il singleton sia a livello di design di classe. In questa fase, dovresti essere in grado di mappare le interazioni tra le classi e vedere se qualcosa di assolutamente, sicuramente richiede che solo 1 istanza di questa classe sia mai presente in qualsiasi momento della vita dell'applicazione.

Se è così, allora hai un singleton. Se stai inserendo singleton per comodità durante la codifica, dovresti davvero rivisitare il tuo design e anche smettere di codificare detti singleton :)

E sì, "polizia" è la parola che intendevo qui piuttosto che "evitare". Il singleton non è qualcosa da evitare (allo stesso modo in cui goto e le variabili globali non sono qualcosa da evitare). Invece, dovresti monitorare il suo utilizzo e assicurarti che sia il metodo migliore per ottenere ciò che desideri sia fatto in modo efficace.


1
Capisco perfettamente cosa stai dicendo, workmad. E sono completamente d'accordo. Tuttavia, la tua risposta non risponde ancora in modo specifico alla mia domanda. Non volevo che questa fosse un'altra domanda su "quando è appropriato usare il singleton?" Ero solo interessato a valide alternative al singleton.
CodingWithoutComments

0

Uso singleton principalmente come "contenitore di metodi", senza alcuno stato. Se ho bisogno di condividere questi metodi con molte classi e voglio evitare il peso della creazione di istanze e inizializzazione, creo un contesto / sessione e inizializzo tutte le classi lì; tutto ciò che si riferisce alla sessione ha accesso anche al "singleton" così contenuto.


0

Non avendo programmato in un ambiente intensamente orientato agli oggetti (ad esempio Java), non sono completamente d'accordo sulle complessità della discussione. Ma ho implementato un singleton in PHP 4. L'ho fatto come un modo per creare un gestore di database "scatola nera" che si inizializzava automaticamente e non doveva essere passato su e giù per le chiamate di funzione in un framework incompleto e in qualche modo rotto.

Avendo letto alcuni link di pattern singleton, non sono del tutto sicuro che lo implementerei di nuovo nello stesso modo. Ciò che era veramente necessario erano più oggetti con archiviazione condivisa (ad esempio l'handle del database effettivo) e questo è praticamente ciò in cui si è trasformata la mia chiamata.

Come la maggior parte dei modelli e degli algoritmi, usare un singleton "solo perché è bello" è The Wrong Thing To Do. Avevo bisogno di una chiamata veramente "scatola nera" che somigliasse molto a un singleton. E IMO questo è il modo per affrontare la domanda: sii consapevole del modello, ma guarda anche al suo ambito più ampio ea quale livello l'istanza deve essere unica.


-1

Cosa vuoi dire, quali sono le mie tecniche per evitarlo?

Per "evitarlo", ciò implica che ci sono molte situazioni in cui mi imbatto in cui il pattern singleton è naturalmente un buon adattamento, e quindi devo prendere alcune misure per disinnescare queste situazioni.

Ma non ci sono. Non devo evitare il pattern singleton. Semplicemente non si presenta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.