Alternative a Singletons / globals


16

Ho sentito innumerevoli volte sulle insidie ​​di Singletons / Global, e capisco perché sono così spesso disapprovati.

Quello che non capisco è quale sia l'alternativa elegante e non disordinata. Sembra che l'alternativa all'utilizzo di Singletons / globals implichi sempre il passaggio di oggetti di un milione di livelli attraverso gli oggetti del motore fino a quando non raggiungono gli oggetti che ne hanno bisogno.

Ad esempio, nel mio gioco, precarico alcune risorse all'avvio del gioco. Queste risorse non vengono utilizzate fino a molto tempo dopo, quando il giocatore naviga attraverso il menu principale ed entra nel gioco. Devo passare questi dati dal mio oggetto di gioco, al mio oggetto ScreenManager (nonostante il fatto che solo uno schermo si preoccupi effettivamente di questi dati), quindi all'oggetto schermo appropriato e in qualsiasi altro luogo?

Sembra solo che io stia scambiando dati sullo stato globale con un'iniezione di dipendenza ingombra, passando i dati ad oggetti a cui non interessano nemmeno i dati, tranne allo scopo di passarli a oggetti figlio.

È un caso in cui un Singleton sarebbe una buona cosa o c'è qualche soluzione elegante che mi manca?

Risposte:


16

Non confondere singletons e globals. Mentre in genere sono necessari alcuni tipi di variabili globali, il singleton non è solo un sostituto di una variabile globale, ma principalmente un modo per aggirare i problemi di ordine di inizializzazione statica in C ++ ( e FQA ). (In altre lingue, è un modo per aggirare carenze linguistiche diverse, come la mancanza di variabili globali e funzioni nude.)

Se puoi semplicemente usare un puntatore globale invece di un singleton e assicurarti che sia inizializzato (manualmente) prima che qualcosa ne abbia bisogno, eviti la chiamata di funzione e l'overhead del ramo, la sintassi lame per arrivare all'oggetto e puoi effettivamente creare un seconda istanza della classe quando è necessario per i test o perché il design è cambiato.

Per le poche variabili globali desiderate (esempi comuni di output audio, elenco di finestre aperte, gestore di tastiera, ecc.), Consiglio il modello di localizzazione del servizio . Semplifica la sostituzione di elementi con diverse implementazioni (ad es. Dispositivo audio reale o nullo) e raccoglie tutti i tuoi globi in una struttura per evitare di inquinare il tuo spazio dei nomi.


+1. Buona risposta e grazie per il collegamento del modello di localizzazione del servizio. È una lettura molto interessante.
Bummzack,

1

Se non hai / non puoi avere una parte del codice magicamente "a conoscenza" di alcuni dati, allora dovrà essere passato in qualche modo. Tuttavia ciò non significa che debba necessariamente passare solo attraverso argomenti.

Nel tuo esempio, non potresti avere una sorta di "AssetManager" che carica e memorizza le risorse, e quindi ScreenManager dovrebbe solo avere un riferimento a quello (probabilmente al momento della creazione)? In questo senso stai passando i riferimenti alle risorse racchiuse in un altro oggetto e puoi passarlo una volta, all'inizializzazione, piuttosto che passarlo alla funzione foglia quando è richiesto.

Ora IMHO che AssetManager, essendo il tipo di cosa di cui vuoi solo uno, potrebbe anche essere un singleton. Purché tu capisca le insidie ​​e specifichi il codice appositamente per evitarle (supponi che a singleton si accederà simultaneamente da più thread e ti pugnalerai con una forchetta ogni volta che fai qualcosa che deve essere bloccato), quindi buttati fuori.


1

Penso che Jason D abbia assolutamente ragione: ecco come lo gestirò:

Il gioco ha un'istanza di AssetManager, un oggetto da cui è possibile ottenere qualsiasi risorsa per nome.

In gioco:

assetManager = new AssetManager();
screenManager = new ScreenManager();
screenManager.assetManager = assetManager;

In ScreenManager:

screen = new Screen();
screen.assetManager = assetManager;

Sullo schermo:

myAsset = assetManager.getBitmp("lava.png");

Ora tutti gli schermi hanno accesso a tutte le risorse di cui hanno bisogno. Questo non è più complesso o folle rispetto all'utilizzo di globi o singleton e hai la possibilità di avere 2 istanze di gioco in esecuzione nella stessa applicazione senza scontri. Una volta ho dovuto creare un gioco composto da 8 minigiochi, tutti con le stesse classi / framework di base. Ho dovuto refactoring tutti i miei globi / singoli per usare questo stile di passaggio di riferimento e non ho mai guardato indietro. Le uniche cose che dovrebbero essere globali sono cose che possono esistere fisicamente una sola volta, come audio, rete, I / O ecc.


0

È possibile utilizzare il modello Factory per sostituire Singleton . Quindi la classe di fabbrica ha il controllo su quante istanze è possibile creare, che è possibile modificare facilmente in seguito quando si scopre che ne occorrono più di una AssetManager. Come affermato in questo articolo :

Ti dà tutta la flessibilità di Singleton, senza nessun problema.


Un'altra possibilità, piuttosto limitata, è quella di rendere statica la classe (che non credo sia fattibile per un AssetManager e possibile solo in linguaggi che hanno classi statiche). Ma funziona solo se non hai bisogno di ereditarietà / polimorfismo. È una soluzione molto inflessibile:

i metodi statici sono flessibili come il granito. Ogni volta che ne usi uno, stai gettando parte del tuo programma in concreto. Assicurati solo di non avere il piede incastrato lì mentre lo guardi indurire. Un giorno rimarrai stupito dal fatto che, davvero, hai davvero bisogno di un'altra implementazione di quella classe di PrintSpooler, e avrebbe dovuto essere un'interfaccia, una fabbrica e un insieme di classi di implementazione. D'oh!

Si tratta di metodi statici, ma può essere applicato anche a classi statiche.


Cosa intendi con "rendere statica la classe"? C ++ non ha classi statiche. Intendi solo metodi statici? Perché preoccuparsi di avere una classe anziché uno spazio dei nomi, allora?

1
@Joe: Beh, la domanda non è focalizzata sul C ++ come l'ho capito. In C # o Java puoi creare classi statiche e mi riferisco a quelle. Inoltre, come ho detto, le classi statiche non sono una soluzione ottimale per la maggior parte del tempo, ma in rari casi potrebbero funzionare come un globale.
Michael Klement,
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.