Alternative al modello singleton

27

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

Una situazione in cui utilizzo i singleton è quando ho bisogno di una fabbrica (diciamo un oggetto f di tipo F) per creare oggetti di una certa classe A. Il factory viene creato una volta utilizzando alcuni parametri di configurazione e quindi viene utilizzato ogni volta che un oggetto di tipo A viene istanziato. Quindi ogni parte di il codice che vuole istanziare 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 è che

  1. Ho bisogno di una sola istanza di una classe per ragioni 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 accedere a questa istanza f di F in diverse parti del codice.

Sono non interessato nella discussione se questo modello è buono o cattivo, ma supponendo che voglio evitare di usare un singleton, quale altro pattern posso usare?

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

In soluzione (1), il registro stesso è un singleton, quindi mi sono appena spostato il problema di non usare un singleton dalla fabbrica al registro.

Nel caso (2) ho bisogno di una fonte iniziale (oggetto) da cui l'oggetto factory viene così ho paura che tornerei di nuovo ad un altro singleton (l'oggetto che fornisce la mia istanza di fabbrica). Seguendo indietro questa catena di singleton posso forse ridurre il problema a un singleton (l'intera applicazione) con cui tutti gli altri singleton sono gestiti direttamente o indirettamente.

Avrebbe questa ultima opzione (usando un singleton iniziale che crea tutto il resto oggetti unici e inietta tutti gli altri singleton a destra luoghi) essere una soluzione accettabile? È questa la soluzione che viene implicitamente suggerita quando si consiglia di non farlo usa singleton o altre soluzioni, ad es. nel l'esempio illustrato sopra?

Modifica

Poiché penso che il punto della mia domanda sia stato frainteso da alcuni, ecco alcune ulteriori informazioni Come spiegato ad es. qui , la parola singleton può indicare (a) una classe con un oggetto di istanza singolo 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 modello singleton per (b). Quindi, so qual è il modello singleton e l'iniezione di dipendenza sono (BTW, ultimamente ho usato pesantemente DI rimuovere le istanze del modello singleton da un codice su cui sto lavorando).

Il mio punto è che a meno che l'intero grafico dell'oggetto non sia istanziato da un singolo oggetto che vive sulla pila 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 completa del grafico dell'oggetto e il cablaggio dipende dal metodo principale (ad esempio attraverso un potente framework DI) quello non usa il modello stesso) è l'unico soluzione per singleton .

    
posta Giorgio 08.05.2012 - 07:41
fonte

9 risposte

7

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

Non puoi aggirare il fatto che qualcosa deve creare la tua fabbrica. Se quel qualcosa 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 lo chiedono per la fabbrica; fai in modo che la tua applicazione crei la fabbrica e la dia a quegli oggetti che ne avranno bisogno.

    
risposta data 08.05.2012 - 08:49
fonte
17

Lo scopo di un singleton è far valere che solo un'istanza può mai esistere all'interno di un certo regno. Ciò significa che un singleton è utile se si hanno forti motivi per imporre il comportamento di singleton; in pratica, questo è raramente il caso, e il multi-processing e il multithreading sfocano certamente nel significato di 'unique' - si tratta di un'istanza per macchina, per processo, per thread, per richiesta? E la tua implementazione singleton si prende cura delle condizioni di gara?

Invece di singleton, preferisco usare uno dei due:

  • istanze locali di breve durata, ad es. per una fabbrica: le classi di fabbrica tipiche hanno quantità minime di stato, se ce ne sono, e non c'è un vero motivo per mantenerle in vita dopo che hanno raggiunto il loro scopo; l'overhead della creazione e dell'eliminazione delle classi non è nulla di cui preoccuparsi nel 99% di tutti gli scenari del mondo reale
  • passando un'istanza in giro, ad es. per un gestore risorse: queste 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 - chissà, forse ha senso avere un secondo gestore risorse a pochi mesi lungo la strada ...

La ragione è che un singleton è uno stato globale sotto mentite spoglie, il che significa che introduce un alto grado di accoppiamento attraverso l'applicazione - qualsiasi parte del codice può catturare l'istanza singleton da qualsiasi luogo con il minimo sforzo. Se si utilizzano oggetti locali o si passano le istanze in giro, si ha un controllo molto maggiore e si può mantenere ristretti i propri ambiti e le proprie dipendenze.

    
risposta data 08.05.2012 - 08:07
fonte
3

La maggior parte delle persone (incluso te) fraintende completamente ciò che è in realtà il modello Singleton. Il pattern Singleton indica 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 tale istanza.

Nel book GoF, il metodo statico di fabbrica che restituisce un riferimento a un campo statico era solo un esempio di come potrebbe essere quel meccanismo, e uno con gravi inconvenienti. Sfortunatamente, tutti e il loro cane si sono agganciati a quel meccanismo e hanno pensato che fosse proprio ciò di cui parla Singleton.

Le alternative che citi sono in realtà anche Singletons, solo con un meccanismo diverso per ottenere il riferimento. (2) risulta chiaramente un eccesso di passaggio, a meno che non sia necessario il riferimento in pochi punti vicino alla radice dello stack di chiamate. (1) suona come una rozza struttura di iniezione di dipendenza - quindi perché non usarne una?

Il vantaggio è che i quadri DI esistenti sono flessibili, potenti e ben collaudati. Possono fare molto di più che gestire semplicemente Singletons. Tuttavia, per sfruttare appieno le loro capacità, funzionano meglio se strutturate la vostra 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 in modo transitorio e sono quindi eseguito.

Modifica In definitiva, tutto dipende comunque dal metodo principale. Ma hai ragione: l'unico modo per evitare completamente l'uso di globale / statico è avere tutto configurato 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 vengono istanziati e richiamati dal codice server. Ma la maggior parte dei framework DI consente anche l'accesso diretto al loro registro centrale, ad es. Spring's ApplicationContext, per "casi speciali".

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

    
risposta data 08.05.2012 - 11:13
fonte
3

Ci sono alcune alternative:

Iniezione di dipendenza

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

Registro di servizio

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

Root Manager

C'è un singolo oggetto che è la radice del grafico dell'oggetto. Seguendo i collegamenti da questo oggetto è possibile trovare qualsiasi altro oggetto. Il codice tende ad assomigliare a:

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()
    
risposta data 08.05.2012 - 22:20
fonte
1

Se le tue esigenze dal singleton possono essere ridotte a una singola funzione, perché non utilizzare semplicemente una semplice funzione di fabbrica? Le funzioni globali (probabilmente un metodo statico di classe F nel tuo esempio) sono intrinsecamente singleton, con l'unicità imposta 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.

Tutto ciò che viene detto, gli articoli 1 e 2 nella tua domanda chiariscono che vuoi davvero solo uno di qualcosa. A seconda della definizione del pattern singleton, lo stai già utilizzando o molto vicino. Non penso che tu possa avere uno di questi 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 questi, quindi forse il problema non è avere una singola istanza di qualcosa, ma di prendere provvedimenti per prevenire (o almeno scoraggiare o minimizzare) l'abuso di esso. Spostare il più possibile il "singleton" e i parametri possibili è un buon inizio. Restringere l'interfaccia al singleton aiuta anche, in quanto vi sono meno opportunità di abuso in questo modo. A volte devi solo succhiarlo e rendere il singleton molto robusto, come l'heap o il filesystem.

    
risposta data 08.05.2012 - 07:52
fonte
1

Se si utilizza 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, un file C ++ con variabili globali libere, funzioni globali libere e variabili statiche usate per nascondere le informazioni, tutto racchiuso in uno spazio dei nomi, ti dà quasi lo stesso effetto di una "classe" singleton.

Si rompe solo se vuoi ereditarietà. Ho visto un sacco di singleton che sarebbero stati migliori in questo modo.

    
risposta data 08.05.2012 - 22:02
fonte
0

Incapsulamento di singleton

Scenario. 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 unico singleton, che ha come membri, gli altri singleton.

In questo modo, invece dei "molti singleton, approccio, contro, nessun singleton, 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 a pseudocodici e ignorano errori minori o errori di sintassi e prendono in considerazione 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.

    
risposta data 08.05.2012 - 21:56
fonte
0

I tuoi requisiti originali:

So the general my scenario is that

  1. I need only one instance of a class either for optimization reasons (I do not need >multiple factory objects) or for sharing common state (e.g. the factory knows how many >instances of A it can still create)
  2. I need a way to have access to this instance f of F in different places of the code.

Non allinearti con la definizione di un singleton (e ciò a cui ti riferisci in seguito). Da GoF (la mia versione è 1995) pagina 127:

Ensure a class only has one instance, and provide a global point of access to it.

Se hai bisogno di una sola istanza, ciò non impedisce a di fare di più.

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

Per evitare l'accessibilità globale, i comuni approcci "crea un'istanza e passa" o "fai combaciare il proprietario di due oggetti insieme"

    
risposta data 08.05.2012 - 22:06
fonte
0

Che ne dici di utilizzare il contenitore IoC? Con un po 'di pensiero si può finire con qualcosa sulle linee di:

Dependency.Resolve<IFoo>(); 

Dovresti specificare quale implementazione di IFoo utilizzare 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 statico del singleton void può essere sostituito con:

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

Il metodo getter singleton statico può essere sostituito con:

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

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

    
risposta data 08.05.2012 - 22:03
fonte