Alternative al modello singleton


27

Ho letto opinioni diverse sul modello singleton. Alcuni sostengono che dovrebbe essere evitato a tutti i costi e altri che può essere utile in determinate situazioni.

Una situazione in cui uso i singleton è quando ho bisogno di una factory (diciamo un oggetto f di tipo F) per creare oggetti di una certa classe A. La factory viene creata una volta usando alcuni parametri di configurazione e quindi viene usata ogni volta che un oggetto di il tipo A è istanziato. Quindi ogni parte del codice che vuole creare un'istanza A recupera il singleton f e crea la nuova istanza, ad es

F& f                   = F::instance();
boost::shared_ptr<A> a = f.createA();

Quindi il mio scenario generale è quello

  1. Ho bisogno di una sola istanza di una classe o per motivi di ottimizzazione (non ho bisogno di più oggetti factory) o per condividere lo stato comune (ad esempio, la factory sa quante istanze di A può ancora creare)
  2. Ho bisogno di un modo per avere accesso a questa istanza f di F in diversi punti del codice.

Non sono interessato alla discussione se questo modello è buono o cattivo, ma supponendo che io voglia evitare di usare un singleton, quale altro modello posso usare?

Le idee che avevo erano (1) di ottenere l'oggetto factory da un registro o (2) di creare la factory ad un certo punto durante l'avvio del programma e quindi passare la factory come parametro.

Nella soluzione (1), il registro stesso è un singleton, quindi ho appena spostato il problema di non utilizzare un singleton dalla fabbrica al registro.

Nel caso (2) ho bisogno di qualche sorgente iniziale (oggetto) da cui proviene l'oggetto factory, quindi temo di ricadere di nuovo in un altro singleton (l'oggetto che fornisce la mia istanza factory). Seguendo questa catena di singleton posso forse ridurre il problema a un singleton (l'intera applicazione) con il quale tutti gli altri singleton sono gestiti direttamente o indirettamente.

Quest'ultima opzione (usando un singleton iniziale che crea tutti gli altri oggetti unici e inietta tutti gli altri singleton nei posti giusti) sarebbe una soluzione accettabile? È questa la soluzione implicitamente suggerita quando si consiglia di non usare i singoli, o quali sono le altre soluzioni, ad esempio nell'esempio sopra illustrato?

MODIFICARE

Dal momento che penso che il punto della mia domanda sia stato frainteso da alcuni, ecco alcune ulteriori informazioni. Come spiegato ad esempio qui , la parola singleton può indicare (a) una classe con un singolo oggetto istanza e (b) un modello di progettazione utilizzato per creare e accedere a tale oggetto.

Per rendere le cose più chiare, usiamo il termine oggetto unico per (a) e il modello singleton per (b). Quindi, so quali sono il pattern singleton e l'iniezione di dipendenza (BTW, ultimamente ho usato DI pesantemente per rimuovere le istanze del pattern singleton da un codice su cui sto lavorando).

Il mio punto è che, a meno che l'intero grafico a oggetti non sia istanziato da un singolo oggetto residente nello stack del metodo principale, ci sarà sempre la necessità di accedere ad alcuni oggetti unici attraverso il modello singleton.

La mia domanda è se avere la creazione e il cablaggio completi degli oggetti grafici dipendono dal metodo principale (ad es. Tramite un potente framework DI che non utilizza il modello stesso) è l'unica soluzione libera per il modello singleton .


8
Iniezione delle dipendenze ...
Falcon,

1
@Falcon: Iniezione delle dipendenze ... cosa? DI non fa nulla per risolvere questo problema, anche se spesso ti offre un modo pratico per nasconderlo.
pdr

@Falcon vuoi dire contenitore IoC? Ciò ti consentirà di risolvere le dipendenze in un'unica riga. Ad esempio Dependency.Resolve <IFoo> (); o Dependency.Resolve <IFoo> () .DoSomething ();
CodeART

Questa è una domanda piuttosto vecchia e ha già risposto. Ho comunque pensato che sarebbe bene collegare questo: stackoverflow.com/questions/162042/…
TheSilverBullet

Risposte:


7

La tua seconda opzione è una buona strada da percorrere: è una sorta di iniezione di dipendenza, che è il modello usato per condividere lo stato nel tuo programma quando vuoi evitare singoli e variabili globali.

Non puoi aggirare il fatto che qualcosa deve creare la tua fabbrica. Se quella cosa sembra essere l'applicazione, così sia. Il punto importante è che la tua fabbrica non dovrebbe preoccuparsi di quale oggetto l'ha creata, e gli oggetti che ricevono la fabbrica non dovrebbero dipendere da dove proviene la fabbrica. Non fare in modo che i tuoi oggetti ottengano un puntatore al singleton dell'applicazione e chiedilo alla fabbrica; fai in modo che la tua applicazione crei la factory e la dia a quegli oggetti che ne avranno bisogno.


17

Lo scopo di un singleton è imporre che una sola istanza possa mai esistere all'interno di un certo regno. Ciò significa che un singleton è utile se si hanno valide ragioni per imporre il comportamento di singleton; in pratica, ciò accade raramente, e il multi-processing e il multi-threading certamente confondono il significato di "univoco" - si tratta di un'istanza per macchina, per processo, per thread, per richiesta? E la tua implementazione singleton si occupa delle condizioni di gara?

Invece di singleton, preferisco usare uno di:

  • istanze locali di breve durata, ad es. per una fabbrica: le classi tipiche di fabbrica hanno una quantità minima di stato, se presente, e non vi è alcun motivo reale per mantenerle in vita dopo aver raggiunto il loro scopo; il sovraccarico della creazione e dell'eliminazione delle classi non è nulla di cui preoccuparsi nel 99% di tutti gli scenari del mondo reale
  • passare un'istanza in giro, ad esempio per un gestore delle risorse: devono essere di lunga durata, perché il caricamento delle risorse è costoso e si desidera mantenerle in memoria, ma non c'è assolutamente alcun motivo per impedire la creazione di ulteriori istanze - chi lo sa, forse avrà senso avere un secondo gestore delle risorse tra qualche mese ...

Il motivo è che un singleton è uno stato globale sotto mentite spoglie, il che significa che introduce un elevato grado di accoppiamento in tutta l'applicazione: qualsiasi parte del codice può catturare l'istanza singleton da qualsiasi luogo con il minimo sforzo. Se usi oggetti locali o passi istanze, hai un controllo molto maggiore e puoi mantenere gli ambiti piccoli e le dipendenze ristrette.


3
Questa è un'ottima risposta, ma non mi chiedevo davvero perché avrei dovuto / non avrei dovuto usare il modello singleton. Conosco i problemi che uno può avere con lo stato globale. Ad ogni modo, l'argomento della domanda era come avere oggetti unici e tuttavia un'implementazione gratuita a schema singleton.
Giorgio,

3

La maggior parte delle persone (incluso te) fraintendono completamente quale sia il modello Singleton. Il modello Singleton significa solo che esiste un'istanza di una classe e che esiste un meccanismo per il codice in tutta l'applicazione per ottenere un riferimento a quell'istanza.

Nel libro GoF, il metodo factory statico che restituisce un riferimento a un campo statico era solo un esempio dell'aspetto di quel meccanismo e uno con gravi inconvenienti. Sfortunatamente, tutti e il loro cane si sono aggrappati a quel meccanismo e hanno pensato che fosse proprio questo a Singleton.

Le alternative che citi sono in realtà anche Singleton, solo con un meccanismo diverso per ottenere il riferimento. (2) si traduce chiaramente in troppi passaggi, a meno che non sia necessario il riferimento in pochi punti vicino alla radice dello stack di chiamate. (1) sembra un framework di iniezione di dipendenza grezza - quindi perché non usarne uno?

Il vantaggio è che i framework DI esistenti sono flessibili, potenti e ben testati. Possono fare molto di più che gestire i Singleton. Tuttavia, per utilizzare appieno le loro capacità, funzionano meglio se strutturi la tua applicazione in un certo modo, il che non è sempre possibile: idealmente, ci sono oggetti centrali che vengono acquisiti attraverso il framework DI e hanno tutte le loro dipendenze popolate transitivamente e sono quindi eseguito.

Modifica: in definitiva, tutto dipende comunque dal metodo principale. Ma hai ragione: l'unico modo per evitare completamente l'uso globale / statico è avere tutto impostato dal metodo principale. Si noti che DI è più popolare negli ambienti server in cui il metodo principale è opaco e imposta il server e il codice dell'applicazione è costituito essenzialmente da callback che sono istanziati e chiamati dal codice server. Ma la maggior parte dei framework DI consente anche l'accesso diretto al loro registro centrale, ad esempio ApplicationContext di Spring, per "casi speciali".

Quindi, in sostanza, la cosa migliore che le persone hanno escogitato finora è una combinazione intelligente delle due alternative che hai citato.


Vuoi dire che l'iniezione di dipendenza dell'idea di base è fondamentalmente il primo approccio che ho delineato sopra (ad esempio usando un registro), finalmente l'idea di base?
Giorgio,

@Giorgio: DI include sempre un tale registro, ma il punto principale che lo rende migliore è che invece di interrogare il registro ovunque sia necessario un oggetto, hai gli oggetti centrali che vengono popolati in modo transitorio, come ho scritto sopra.
Michael Borgwardt,

@Giorgio: è più facile da capire con un esempio concreto: supponiamo che tu abbia un'app Java EE. La tua logica front-end è in un metodo di azione di un bean gestito JSF. Quando l'utente preme un pulsante, il contenitore JSF crea un'istanza del bean gestito e chiama il metodo action. Ma prima, il componente DI guarda i campi della classe Managed Bean e quelli che hanno una certa annotazione sono popolati con un riferimento a un oggetto Service secondo la configurazione DI, e quegli oggetti Service hanno la stessa cosa succedendo loro . Il metodo di azione può quindi chiamare i servizi.
Michael Borgwardt,

Alcune persone (incluso te) non leggono attentamente le domande e fraintendono ciò che viene effettivamente chiesto.
Giorgio,

1
@Giorgio: nulla nella tua domanda originale indicava che hai già familiarità con l'iniezione di dipendenza. E vedi la risposta aggiornata.
Michael Borgwardt,

3

Ci sono alcune alternative:

Iniezione di dipendenza

Ogni oggetto ha le sue dipendenze passate quando è stato creato. In genere un framework o un numero limitato di classi di fabbrica sono responsabili della creazione e del cablaggio degli oggetti

Registro dei servizi

A ogni oggetto viene passato un oggetto del registro di servizio. Fornisce metodi per richiedere vari oggetti diversi che forniscono servizi diversi. Il framework Android utilizza questo modello

Gestore radice

C'è un singolo oggetto che è la radice del grafico degli oggetti. Seguendo i collegamenti da questo oggetto è possibile trovare qualsiasi altro oggetto. Il codice tende ad apparire come:

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()

A parte il deridere, quale vantaggio ha DI rispetto all'utilizzo di un singleton? Questa è una domanda che mi ha infastidito da molto tempo. Per quanto riguarda il registro e il root manager, non sono implementati come singleton o tramite DI? (In effetti, il contenitore IoC è un registro, giusto?)
Konrad Rudolph

1
@KonradRudolph, con DI le dipendenze sono esplicite e l'oggetto non fa ipotesi su come viene fornita una determinata risorsa. Con i singoli, le dipendenze sono implicite e ogni singolo uso presuppone che ci sarà sempre e solo un oggetto del genere.
Winston Ewert,

@KonradRudolph, gli altri metodi sono implementati usando Singletons o DI? Sì e No. Alla fine, devi passare oggetti o archiviare oggetti nello stato globale. Ma ci sono sottili differenze nel modo in cui lo fai. Le tre versioni menzionate sopra evitano di usare lo stato globale; ma il modo in cui il codice finale ottiene i riferimenti è diverso.
Winston Ewert,

L'uso di singleton non presuppone alcun singolo oggetto se singleton implementa un'interfaccia (o anche in caso contrario) e si passa l'istanza singleton ai metodi, invece di eseguire query Singleton::instanceovunque nel codice client.
Konrad Rudolph,

@KonradRudolph, allora stai davvero usando un approccio ibrido Singleton / DI. La mia parte purista pensa che dovresti scegliere un approccio DI puro. Tuttavia, la maggior parte dei problemi con singleton non si applica davvero lì.
Winston Ewert,

1

Se le tue esigenze dal singleton possono essere ridotte a una singola funzione, perché non usare una semplice funzione di fabbrica? Le funzioni globali (probabilmente un metodo statico di classe F nel tuo esempio) sono intrinsecamente singole, con unicità applicata dal compilatore e dal linker.

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

Certo, questo si interrompe quando l'operazione del singleton non può essere ridotta a una singola chiamata di funzione, anche se forse un piccolo insieme di funzioni correlate potrebbe farti passare.

Detto questo, gli articoli 1 e 2 nella tua domanda chiariscono che vuoi davvero uno di qualcosa. A seconda della definizione del modello singleton, lo stai già utilizzando o molto vicino. Non penso che tu possa avere uno di qualcosa senza che sia un singleton o almeno molto vicino a uno. È troppo vicino al significato di singleton.

Come suggerisci, ad un certo punto devi avere uno di qualcosa, quindi forse il problema non è avere una singola istanza di qualcosa, ma adottare misure per prevenire (o almeno scoraggiarlo o minimizzarlo). Spostare più stato dal "singleton" e nei parametri possibili è un buon inizio. Anche restringere l'interfaccia al singleton aiuta, poiché ci sono meno opportunità di abuso in quel modo. A volte devi solo succhiarlo e rendere il singleton molto robusto, come l'heap o il filesystem.


4
Personalmente vedo questo come una diversa implementazione del modello singleton. In questo caso l'istanza singleton è la classe stessa. In particolare: ha tutti gli stessi inconvenienti di un singleton "reale".
Joachim Sauer

@ Rich Cook: Penso che la tua risposta capisca il mio punto. Ho letto molto spesso che il singleton è molto brutto e dovrebbe essere evitato a tutti i costi. Sono d'accordo su questo, ma d'altra parte penso che sia tecnicamente impossibile evitarlo sempre. Quando hai bisogno di "uno di qualcosa" devi crearlo da qualche parte e accedervi in ​​qualche modo.
Giorgio,

1

Se stai usando un linguaggio multiparadigm come C ++ o Python, un'alternativa a una classe singleton è un insieme di funzioni / variabili racchiuse in uno spazio dei nomi.

Concettualmente parlando, un file C ++ con variabili globali libere, funzioni globali libere e variabili statiche usate per nascondere le informazioni, il tutto racchiuso in uno spazio dei nomi, ti dà quasi lo stesso effetto di una "classe" singleton.

Si rompe solo se si desidera l'eredità. Ho visto molti singoli che sarebbero stati meglio così.


0

Incapsulamento di Singletons

Scenario del caso La tua applicazione è orientata agli oggetti e richiede 3 o 4 singleton specifici, anche se hai più classi.

Prima dell'esempio (C ++ come pseudocodice):

// network info
class Session {
  // ...
};

// current user connection info
class CurrentUser {
  // ...
};

// configuration upon user
class UserConfiguration {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
  // ...
};

Un modo è rimuovere parzialmente alcuni singleton (globali), incapsulandoli tutti in un singleton unico, che ha come membri gli altri singleton.

In questo modo, invece di "diversi singleton, approccio, contro, nessun singleton affatto, approccio", abbiamo "incapsulare tutti i singleton in uno, approccio".

Dopo l'esempio (C ++ come pseudocodice):

// network info
class NetworkInfoClass {
  // ...
};

// current user connection info
class CurrentUserClass {
  // ...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
  // ...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

Si noti che gli esempi sono più simili allo pseudocodice e ignorano bug minori o errori di sintassi e considerano la soluzione alla domanda.

C'è anche un'altra cosa da considerare, perché il linguaggio di programmazione utilizzato può influire su come implementare l'implementazione singleton o non singleton.

Saluti.


0

I tuoi requisiti originali:

Quindi il mio scenario generale è quello

  1. Ho bisogno di una sola istanza di una classe o per motivi di ottimizzazione (non ho bisogno di> più oggetti factory) o per condividere lo stato comune (es. La factory sa quante> istanze di A può ancora creare)
  2. Ho bisogno di un modo per avere accesso a questa istanza f di F in diversi punti del codice.

Non allinearti con la definizione di singleton (e cosa preferisci fare riferimento in seguito). Da GoF (la mia versione è 1995) pagina 127:

Assicurati che una classe abbia solo un'istanza e fornisci un punto di accesso globale ad essa.

Se hai solo bisogno di un'istanza, ciò non ti impedisce di fare di più.

Se desideri un'unica istanza accessibile a livello globale, crea una singola istanza accessibile a livello globale . Non è necessario avere un modello chiamato per tutto ciò che fai. E sì, le singole istanze accessibili a livello globale sono generalmente sbagliate. Dare loro un nome non li rende meno cattivi .

Per evitare l'accessibilità globale, il comune approccio "crea un'istanza e passala" o "fai in modo che il proprietario di due oggetti li incolli insieme" funzioni bene.


0

Che ne dici di usare il contenitore IoC? Con qualche pensiero puoi finire con qualcosa sulle linee di:

Dependency.Resolve<IFoo>(); 

Dovresti specificare quale implementazione IFoousare all'avvio, ma questa è una configurazione unica che puoi facilmente sostituire in seguito. La durata di un'istanza risolta può essere normalmente controllata da un contenitore IoC.

Il metodo vuoto statico singleton potrebbe essere sostituito con:

Dependency.Resolve<IFoo>().DoSomething();

Il metodo statico getter singleton potrebbe essere sostituito con:

var result = Dependency.Resolve<IFoo>().GetFoo();

L'esempio è rilevante per .NET, ma sono certo che si può ottenere molto simile in altre lingue.

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.