Come posso evitare di avere molti singoli nella mia architettura di gioco?


55

Uso il motore di gioco cocos2d-x per creare giochi. Il motore utilizza già molti singleton. Se qualcuno l'ha usato, allora dovrebbe avere familiarità con alcuni di loro:

Director
SimpleAudioEngine
SpriteFrameCache
TextureCache
EventDispatcher (was)
ArmatureDataManager
FileUtils
UserDefault

e molti altri con complessivamente circa 16 lezioni. Puoi trovare un elenco simile in questa pagina: oggetti Singleton in Cocos2d-html5 v3.0 Ma quando voglio scrivere il gioco ho bisogno di molti più singoli:

PlayerData (score, lives, ...)
PlayerProgress (passed levels, stars)
LevelData (parameters per levels and level packs)
SocialConnection (Facebook and Twitter login, share, friend list, ...)
GameData (you may obtain some data from server to configure the game)
IAP (for in purchases)
Ads (for showing ads)
Analytics (for collecting some analytics)
EntityComponentSystemManager (mananges entity creation and manipulation)
Box2dManager (manages the physics world)
.....

Perché penso che dovrebbero essere single? Perché ne avrò bisogno in posti molto diversi nel mio gioco e l'accesso condiviso sarebbe molto utile. In altre parole, non so cosa crearli da qualche parte e passare i puntatori verso il basso su tutta la mia architettura in quanto sarà molto difficile. Anche queste sono cose di cui ho bisogno solo di una. In ogni caso ne avrò bisogno diversi, posso usare anche un modello Multiton. Ma la cosa peggiore è che Singleton è il modello più fortemente criticato a causa di:

 - bad testability
 - no inheritance available
 - no lifetime control
 - no explicit dependency chain
 - global access (the same as global variables, actually)
 - ....

Puoi trovare alcuni pensieri qui: https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons e https://stackoverflow.com/questions/4074154/when-should-the-singleton -pattern-non-essere-usato-oltre-il-ovvio

Quindi, penso, sto facendo qualcosa di sbagliato. Penso che il mio codice abbia un odore . :) Mi ferisco come gli sviluppatori di giochi più esperti risolvono questo problema architettonico? Voglio verificare, forse è ancora normale nello sviluppo del gioco avere più di 30 singoli , considerati quelli che sono già nel motore di gioco.

Ho pensato di usare una facciata di Singleton che avrà istanze di tutte queste classi di cui ho bisogno, ma ognuna di esse non sarebbe già un singolo. Ciò eliminerà molti problemi e avrei un solo singleton che sarebbe la facciata stessa. Ma in questo caso avrò un altro problema di progettazione. La facciata diventerà un OGGETTO DIO. Penso che anche questo abbia un odore . Quindi non riesco a trovare una buona soluzione progettuale per questa situazione. Per favore, consiglio.


26
La maggior parte delle persone che discutono contro Singleton non sembrano rendersi conto che il lavoro richiesto per propagare alcuni dati / funzionalità attraverso tutto il codice potrebbe essere più una fonte di bug che usare un Singleton. Quindi sostanzialmente non stanno confrontando, ma semplicemente rifiutando ==> sospetto che l'anti-singletonismo sia una postura :-) Quindi bene, Singleton ha dei difetti, quindi se puoi evitarlo, fallo in modo da evitare i problemi che ne derivano , ora se non puoi, provaci senza guardare indietro. Dopo tutto, Cocos2d sembra funzionare abbastanza bene con i suoi 16 Singletons ...
GameAlchemist

6
Come promemoria, questa è una domanda su come eseguire una determinata attività. Questa non è una domanda che richiede una discussione pro / contro dei singoli (che sarebbe comunque fuori tema qui). Inoltre, ricorda che i commenti dovrebbero essere usati per richiedere chiarimenti a chi chiede, non come piattaforma per una discussione tangenziale sui pro e contro dei singoli. Se si desidera fornire il proprio contributo, il modo corretto per farlo è quello di fornire una risposta legittima alla domanda , in cui è possibile temperare la soluzione con consigli a favore o contro il modello singleton.
Josh

Presumo che questi singleton siano quindi accessibili a livello globale? Quale sarebbe una ragione per evitare quel modello.
Peter - Ripristina Monica il

Risposte:


41

Inizializzo i miei servizi nella mia classe di applicazione principale e quindi li passo come puntatori a tutto ciò che serve per usarli attraverso i costruttori o le funzioni. Questo è utile per due motivi.

Uno, l'ordine di inizializzazione e pulizia è semplice e chiaro. Non è possibile inizializzare accidentalmente un servizio altrove come è possibile con un singleton.

Due, è chiaro chi dipende da cosa. Posso facilmente vedere quali classi hanno bisogno di quali servizi solo dalla dichiarazione di classe.

Potresti pensare che sia fastidioso passare tutti questi oggetti in giro, ma se il sistema è progettato bene, non è affatto male e rende le cose molto più chiare (per me comunque).


34
+1. Inoltre, il "fastidio" di dover passare i riferimenti ai sistemi intorno aiuta a contrastare il creep di dipendenza; se devi passare qualcosa a " tutto " , questo è un buon segno che hai un cattivo problema di accoppiamento nel tuo progetto, ed è molto più esplicito in questo modo che con un accesso globale promiscuo a tutto da qualsiasi luogo.
Josh

2
Questo in realtà non fornisce una soluzione ... quindi hai la tua classe di programma con un mucchio di oggetti singleton e ora 10 livelli di codice nel codice che qualcosa nella scena ha bisogno di uno di quei singleton ... come viene passato?
Guerra

Wardy: Perché dovrebbero essere single? Certo, per lo più potresti passare intorno a un'istanza particolare, ma ciò che rende un singleton un singleton è che NON PUOI utilizzare un'istanza diversa. Con l'iniezione, è possibile passare un'istanza per un oggetto e un'altra per il successivo. Solo perché non finisci per farlo per qualsiasi motivo, non significa che non puoi.
Parar,

3
Non sono single. Sono oggetti regolari come qualsiasi altra cosa con un proprietario ben definito che controlla init / cleanup. Se hai 10 livelli di profondità senza accesso, forse hai un problema di progettazione. Non tutto ha bisogno o dovrebbe avere accesso a renderer, audio e così via. In questo modo ti fa pensare al tuo design. Come bonus aggiuntivo per coloro che testano il loro codice (cosa ??), anche i test unitari diventano molto più facili.
Megadan,

2
Sì, penso che l'iniezione di dipendenza (con o senza un framework come Spring) sia una soluzione perfetta per gestirla. Ho trovato questo un ottimo articolo per coloro che si chiedono come strutturare meglio il codice per evitare di passare le cose ovunque: misko.hevery.com/2008/10/21/…
megadan

17

Non discuterò della malvagità dietro i singoli perché Internet può farlo meglio di me.

Nei miei giochi utilizzo il modello Service Locator per evitare di avere tonnellate di Singleton / Manager.

Il concetto è piuttosto semplice. Hai solo un Singleton che si comporta come l'unica interfaccia per raggiungere quello che hai usato come Singleton. Invece di avere diversi Singleton ora ne hai solo uno.

Chiamate come:

FlyingToasterManager.GetInstance().Toast();
SmokeAlarmManager.GetInstance().Start();

Assomiglia a questo, usando il modello Localizzatore servizi:

SvcLocator.FlyingToaster.Toast();
SvcLocator.SmokeAlarm.Start();

Come puoi vedere, ogni Singleton è contenuto nel Service Locator.

Per una spiegazione più dettagliata ti suggerisco di leggere questa pagina su come usare il modello di localizzazione .

[ EDIT ]: come sottolineato nei commenti, il sito gameprogrammingpatterns.com contiene anche una sezione molto interessante su Singleton .

Spero possa essere d'aiuto.


4
Il sito collegato qui, gameprogrammingpatterns.com, ha anche un capitolo su Singletons che sembra rilevante.
ajp15243,

28
I localizzatori di servizio sono solo singoli punti che spostano tutti i normali problemi in fase di esecuzione anziché in fase di compilazione. Tuttavia, ciò che stai suggerendo è solo un gigantesco registro statico, che è quasi migliore, ma essenzialmente una massa di variabili globali. Questa è semplicemente una questione di cattiva architettura se molti livelli hanno bisogno dell'accesso agli stessi oggetti. L'iniezione di dipendenza corretta è ciò che è necessario qui, non globalmente denominati in modo diverso rispetto a quelli attuali.
Magus,

2
Puoi approfondire la "corretta iniezione di dipendenza"?
Mago blu,

17
Questo non aiuta davvero il problema. Sono essenzialmente molti singleton fusi in un singleton gigante. Nessuno dei problemi strutturali sottostanti è stato risolto o addirittura risolto, il codice ora puzza ancora di più.
ClassicThunder,

4
@classicthunder Anche se questo non affronta ogni singolo problema, si tratta di un grande miglioramento rispetto a Singletons perché risolve diversi problemi. In particolare, il codice che scrivi non è più accoppiato a una singola classe, ma piuttosto a un'interfaccia / categoria di classi. Ora puoi facilmente scambiare diverse implementazioni dei vari servizi.
jhocking

12

Leggendo tutte queste risposte, commenti e articoli sottolineati, in particolare questi due articoli geniali,

alla fine sono giunto alla seguente conclusione, che è una specie di risposta alla mia domanda. L'approccio migliore non è essere pigri e passare dipendenze direttamente. Questo è esplicito, verificabile, non esiste un accesso globale, può indicare una progettazione errata se si devono passare delegati molto profondi e in molti luoghi e così via. Ma nella programmazione una taglia non va bene per tutti. Pertanto, ci sono ancora casi in cui non è utile passarli direttamente. Ad esempio nel caso in cui creiamo un framework di gioco (un modello di codice che verrà riutilizzato come codice di base per sviluppare diversi tipi di giochi) e includiamo molti servizi. Qui non sappiamo quanto in profondità ogni servizio può essere passato e in quanti luoghi sarà necessario. Dipende da un gioco particolare. Quindi cosa facciamo in questo tipo di casi? Usiamo il modello di progettazione di Service Locator anziché Singleton,

  1. Non programmate sulle implementazioni ma sulle interfacce. Questo è molto essenziale in termini di principi OOP. In fase di esecuzione è possibile modificare o addirittura disabilitare il servizio utilizzando il modello Null Object. Questo non è solo importante in termini di flessibilità, ma aiuta direttamente nei test. È possibile disabilitare, simulare, inoltre è possibile utilizzare il modello Decorator, per aggiungere anche alcune funzionalità di registrazione e altre. Questa è una proprietà molto importante, che Singleton non ha.
  2. Un altro enorme vantaggio è che puoi controllare la durata dell'oggetto. In Singleton controlli solo il tempo in cui l'oggetto verrà generato dal modello di inizializzazione pigra. Ma una volta generato l'oggetto, vivrà fino alla fine del programma. Ma in alcuni casi ti piacerebbe cambiare il servizio. O addirittura spegnerlo. Soprattutto nei giochi questa flessibilità è molto importante.
  3. Combina / avvolge diversi Singleton in un'unica API compatta. Penso che potresti anche utilizzare alcuni localizzatori di servizi come facciate, che per una singola operazione potrebbero utilizzare più servizi contemporaneamente.
  4. Potresti sostenere che abbiamo ancora l'accesso globale, che è il principale problema di progettazione con Singleton. Sono d'accordo, ma avremo bisogno di questo modello solo e solo se vogliamo un accesso globale. Non dovremmo lamentarci dell'accesso globale, se riteniamo che l'iniezione di dipendenza diretta non sia utile per la nostra situazione e abbiamo bisogno di un accesso globale. Ma anche per questo problema, è disponibile un miglioramento (vedere il secondo articolo sopra). Puoi rendere privati ​​tutti i servizi e puoi ereditare dalla classe Service Locator tutte le classi che ritieni debbano utilizzare i servizi. Questo può restringere significativamente l'accesso. E ti preghiamo di considerare che nel caso di Singleton non puoi usare l'eredità a causa del costruttore privato.

Inoltre, dovremmo considerare che questo modello è stato utilizzato in Unity, LibGDX, XNA. Questo non è un vantaggio, ma è una specie di prova dell'usabilità del modello. Considera che questi motori sono stati sviluppati da molti sviluppatori intelligenti da molto tempo e durante le fasi di evoluzione del motore non hanno ancora trovato alcuna soluzione migliore.

In conclusione, penso che Service Locator possa essere molto utile per gli scenari descritti nella mia domanda, ma dovrebbe comunque essere evitato, se possibile. Come regola generale, usalo se devi.

EDIT: dopo un anno di lavoro e utilizzo di DI diretto e problemi con costruttori enormi e molte delegazioni, ho fatto ulteriori ricerche e ho trovato un buon articolo che parla di pro e contro sui metodi DI e Service Locator. Citando questo articolo ( http://www.martinfowler.com/articles/injection.html ) di Martin Fowler che spiega quando effettivamente i locatori di servizio sono cattivi:

Quindi il problema principale è per le persone che stanno scrivendo codice che prevede di essere utilizzato in applicazioni al di fuori del controllo dello scrittore. In questi casi anche un presupposto minimo su un Service Locator è un problema.

Ma comunque, se ci vuoi DI prima volta, dovresti leggere questo http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/ articolo (sottolineato da @megadan) , prestando molta attenzione alla Legge di Demetra (LoD) o al principio della minima conoscenza .


La mia intolleranza personale nei confronti dei localizzatori di servizi deriva principalmente dai miei tentativi di testare il codice che li utilizza. Nell'interesse del test black-box, si chiama il costruttore per creare un'istanza da testare. Un'eccezione può essere generata immediatamente o su qualsiasi chiamata di metodo e potrebbero non esserci proprietà pubbliche che conducano alle dipendenze mancanti. Questo può essere minore se hai accesso alla fonte, eppure viola ancora il Principio di Least Surprise (un problema frequente con Singletons). Hai suggerito di passare al localizzatore di servizio, che allevia parte di questo, e devi essere lodato per questo.
Magus,

3

Non è raro che parti di una base di codice siano considerate oggetti di pietra angolare o una classe di fondazione, ma ciò non giustifica il suo ciclo di vita da dettare come Singleton.

I programmatori spesso fanno affidamento sul modello Singleton come mezzo di convenienza e pura pigrizia piuttosto che adottare un approccio alternativo ed essere un po 'più prolissi e imponenti relazioni oggettuali tra loro in un maniero esplicito.

Quindi chiediti quale è più facile da mantenere.

Se sei come me, preferisco essere in grado di aprire un file di intestazione e sfogliare brevemente argomenti del costruttore e metodi pubblici per vedere quali dipendenze richiede un oggetto piuttosto che dover ispezionare centinaia di righe di codice in un file di implementazione.

Se hai trasformato un oggetto in Singleton e in seguito ti rendi conto che devi essere in grado di supportare più istanze di detto oggetto, immagina i cambiamenti radicali necessari se questa classe fosse qualcosa che hai usato abbastanza pesantemente nella tua base di codice. La migliore alternativa è rendere esplicite le dipendenze, anche se l'oggetto viene allocato una sola volta e se si presenta la necessità di supportare più istanze, è possibile farlo in sicurezza senza tali preoccupazioni.

Come altri hanno sottolineato, l'uso del modello Singleton offusca anche l'accoppiamento che spesso indica un design scadente e un'elevata coesione tra i moduli. Tale coesione è generalmente indesiderabile e porta a un codice fragile che può essere difficile da mantenere.


-1: questa risposta descrive erroneamente il modello singleton e tutti i suoi argomenti descrivono i problemi con una scarsa implementazione del modello. Il singleton corretto è un'interfaccia ed evita ogni singolo problema che hai descritto (incluso il passaggio alla non-singolarità, che è uno scenario che GoF affronta esplicitamente). Se è difficile riformattare l'uso di un singleton, il refactoring dell'uso di un riferimento a un oggetto esplicito può essere solo più difficile, non meno. Se singleton in qualche modo provoca l'accoppiamento del codice PIÙ, è stato semplicemente fatto male.
Seth Battin,

1

Singleton è un modello famoso, ma è bene conoscere gli scopi che serve e i suoi pro e contro.

Ha davvero senso se non c'è alcuna relazione .
Se riesci a gestire il tuo componente con un oggetto totalmente diverso (nessuna dipendenza forte) e ti aspetti di avere lo stesso comportamento, il singleton potrebbe essere una buona scelta.

D'altra parte, se hai bisogno di una piccola funzionalità , utilizzata solo in determinati momenti, dovresti preferire passare i riferimenti .

Come hai detto, i singoli non hanno alcun controllo a vita. Ma il vantaggio è che hanno un'inizializzazione pigra.
Se non si chiama quel singleton nella vita dell'applicazione, non verrà istanziato. Ma dubito che tu costruisca singleton e non li usi.

È necessario visualizzare un singleton come un servizio anziché un componente .

Singletons funziona, non hanno mai rotto nessuna applicazione. Ma le loro carenze (le quattro che hai dato) sono ragioni per cui non ne farai una.


1

Il motore utilizza già molti singleton. Se qualcuno l'ha usato, allora dovrebbe avere familiarità con alcuni di loro:

La non familiarità non è il motivo per cui i singoli devono essere evitati.

Ci sono molte buone ragioni per cui Singleton è necessario o inevitabile. I framework di gioco usano spesso i singleton perché è inevitabile conseguenza dell'avere un solo hardware con stato. Non ha senso voler mai controllare questi hardware con più istanze dei rispettivi gestori. La superficie grafica è un hardware con stato esterno e l'inizializzazione accidentale di una seconda copia del sottosistema grafico è a dir poco disastrosa, poiché ora i due sottosistemi grafici si combatteranno su chi può disegnare e quando, sovrascrivendosi in modo incontrollabile. Allo stesso modo con il sistema di code degli eventi, si batteranno su chi ottiene gli eventi del mouse in modo non deterministico. Quando si ha a che fare con un hardware esterno con stato in cui ce n'è solo uno, è inevitabile singleton per prevenire conflitti.

Un altro posto in cui Singleton è ragionevole è con i gestori della cache. Le cache sono un caso speciale. Non dovrebbero davvero essere considerati un Singleton, anche quando usano tutte le stesse tecniche dei Singleton per rimanere in vita e vivere per sempre. I servizi di cache sono servizi trasparenti, non dovrebbero alterare il comportamento del programma, quindi se si sostituisce il servizio cache con una cache nulla, il programma dovrebbe comunque funzionare, tranne per il fatto che funziona solo più lentamente. Il motivo principale per cui i gestori di cache sono un'eccezione a singleton è perché non ha senso chiudere un servizio cache prima di chiudere l'applicazione stessa perché ciò eliminerà anche gli oggetti memorizzati nella cache, il che vanifica il punto di averlo come singleton.

Questi sono buoni motivi per cui questi servizi sono singoli. Tuttavia, nessuno dei buoni motivi per avere i singoli si applica alle classi che hai elencato.

Perché ne avrò bisogno in posti molto diversi nel mio gioco e l'accesso condiviso sarebbe molto utile.

Quelle non sono ragioni per i singoli. Queste sono le ragioni per i globali. È anche un segno di cattiva progettazione se è necessario passare molte cose intorno a vari sistemi. Dover passare le cose in giro indica un alto accoppiamento che può essere impedito da un buon design OO.

Solo guardando l'elenco delle lezioni nel tuo post che pensi debbano essere Singletons, posso dire che la metà di loro non dovrebbe essere in realtà singleton e l'altra metà sembra che non dovrebbero nemmeno essere lì in primo luogo. Il fatto che sia necessario far passare oggetti in giro sembra essere dovuto alla mancanza di incapsulamento corretto piuttosto che a casi di utilizzo effettivo per singoli.

Diamo un'occhiata alle tue lezioni poco a poco:


PlayerData (score, lives, ...)
LevelData (parameters per levels and level packs)
GameData (you may obtain some data from server to configure the game)

Solo dai nomi delle classi, direi che queste classi hanno l'odore di antipasto di Anemic Domain Model . Gli oggetti anemici di solito producono molti dati che devono essere passati, il che aumenta l'accoppiamento e rende ingombrante il resto del codice. Inoltre, la classe anemica nasconde il fatto che probabilmente stai ancora pensando in modo procedurale piuttosto che usare l'orientamento agli oggetti per incapsulare i dettagli.


IAP (for in purchases)
Ads (for showing ads)

Perché queste lezioni devono essere dei singoli? Mi sembra che queste dovrebbero essere classi di breve durata, che dovrebbero essere sollevate quando sono necessarie e decostruite quando l'utente termina l'acquisto o quando gli annunci non devono più essere mostrati.


EntityComponentSystemManager (mananges entity creation and manipulation)

In altre parole, un costruttore e un livello di servizio? Perché questa classe non ha confini o scopi chiari nemmeno lì in primo luogo?


PlayerProgress (passed levels, stars)

Perché il progresso del giocatore è separato dalla classe del Giocatore? La classe Player dovrebbe sapere come tenere traccia dei propri progressi, se si desidera implementare il tracciamento dei progressi in una classe diversa rispetto alla classe Player per la separazione delle responsabilità, PlayerProgress dovrebbe essere dietro Player.


Box2dManager (manages the physics world)

Non posso commentare ulteriormente questo argomento senza sapere cosa fa realmente questa classe, ma una cosa è chiara è che questa classe ha un nome mediocre.


Analytics (for collecting some analytics)
SocialConnection (Facebook and Twitter login, share, friend list, ...)

Questa sembra essere l'unica classe in cui Singleton può essere ragionevole, perché gli oggetti di connessione sociale non sono in realtà i tuoi oggetti. Le connessioni sociali sono solo un'ombra di oggetti esterni che vivono in un servizio remoto. In altre parole, questo è un tipo di cache. Assicurati di separare chiaramente la parte di cache con la parte di servizio del servizio esterno, poiché quest'ultima parte non dovrebbe essere un singleton.

Un modo per evitare di passare istanze di queste classi è usare il passaggio dei messaggi. Anziché effettuare una chiamata diretta alle istanze di queste classi, si invia invece un messaggio indirizzato al servizio Analitico e SocialConnection in modo asincrono e questi servizi sono abbonati per ricevere questi messaggi e agire sul messaggio. Poiché la coda degli eventi è già un singleton, trampolando la chiamata, si evita la necessità di passare un'istanza effettiva quando si comunica con un singleton.


-1

I singleton per me sono SEMPRE cattivi.

Nella mia architettura ho creato una raccolta GameServices gestita dalla classe di gioco. Posso cercare aggiungere e rimuovere da questa raccolta come richiesto.

Dicendo che qualcosa ha una portata globale, in pratica stai dicendo "questo hting è dio e non ha padrone" negli esempi che fornisci sopra direi che la maggior parte di questi sono classi di aiuto o GameServices, nel qual caso il gioco sarebbe responsabile per loro.

Ma questo è solo il mio progetto nel mio motore, la tua situazione potrebbe essere diversa.

Mettendolo più direttamente ... L'unico vero singleton è il gioco in quanto possiede ogni parte del codice.

Questa risposta si basa sulla natura soggettiva della domanda e si basa esclusivamente sulla mia opinione, potrebbe non essere corretta per tutti gli sviluppatori là fuori.

Modifica: come richiesto ...

Ok, questo è dalla mia testa dato che al momento non ho il mio codice davanti a me, ma essenzialmente alla base di ogni gioco c'è la classe di gioco che è davvero un singleton ... deve essere!

Quindi ho aggiunto il seguente ...

class Game
{
   IList<GameService> Services;

   public T GetService<T>() where T : GameService
   {
       return Services.FirstOrDefatult(s => s is T);
   }
}

Ora ho anche una classe di sistema (statica) che contiene tutti i miei singoli da tutto il programma (contiene pochissime opzioni di gioco e di configurazione e questo è tutto) ...

public static class Sys
{
    // only the main game assembly can set this (done by the game ctor)
    // anything can get
    public static Game Game { get; internal set; }
}

E utilizzo ...

Da qualsiasi parte posso fare qualcosa del genere

var tService = Sys.Game.getService<T>();
tService.Something();

Questo approccio significa che il mio gioco "possiede" elementi che in genere sarebbero considerati un "singleton" e posso estendere la classe di GameService di base come voglio. Ho interfacce come IUpdateable che il mio gioco verifica e chiama a tutti i servizi che lo implementano in base alle necessità per situazioni specifiche.

Ho anche servizi che ereditano / estendono altri servizi per scenari di scene più specifici.

Per esempio ...

Ho un servizio di fisica che gestisce le mie simulazioni fisiche, ma a seconda della simulazione fisica della scena potrebbe essere necessario un comportamento leggermente diverso.


1
I servizi di gioco non dovrebbero essere istanziati una sola volta? In tal caso, è solo un modello singleton non applicato a livello di classe, il che è peggio di un singleton correttamente implementato.
GameAlchemist,

2
@Wardy Non capisco chiaramente cosa hai fatto, ma sembra la facciata Singleton che ho descritto e dovrebbe diventare un oggetto DIO, giusto?
Narek,

10
È solo un modello Singleton implementato a livello di gioco. Quindi in pratica il suo aspetto più interessante è permetterti di far finta di non usare Singleton. Utile se il tuo capo viene dalla chiesa anti-schema.
GameAlchemist,

2
Non sono sicuro di come sia effettivamente diverso dall'uso dei Singleton. Non è l'unica differenza nel modo in cui accedi al tuo singolo servizio esistente di questo tipo? Vale a dire var tService = Sys.Game.getService<T>(); tService.Something();invece di saltare la getServiceparte e accedervi direttamente?
Christian,

1
@Christian: In effetti, questo è esattamente ciò che è l'antipasto Service Locator. Apparentemente questa comunità pensa ancora che sia un modello di progettazione raccomandato.
Magus,

-1

Un ingrediente essenziale dell'eliminazione dei singleton che spesso gli avversari del singleton non riescono a considerare è l'aggiunta di una logica aggiuntiva per gestire lo stato molto più complicato che gli oggetti incapsulano quando i singleton cedono il passo ai valori di istanza. In effetti, anche definire lo stato di un oggetto dopo aver sostituito un singleton con una variabile di istanza può essere difficile, ma i programmatori che sostituiscono i singleton con variabili di istanza dovrebbero essere pronti a gestirlo.

Confronta, ad esempio, Java HashMapcon .NET Dictionary. Entrambi sono abbastanza simili, ma hanno una differenza fondamentale: Java HashMapè hardcoded da usare equalse hashCode(utilizza effettivamente un comparatore singleton) mentre .NET Dictionarypuò accettare un'istanza di IEqualityComparer<T>come parametro del costruttore. Il costo di runtime di mantenere il IEqualityComparerè minimo, ma la complessità che introduce non lo è.

Poiché ogni Dictionaryistanza incapsula un IEqualityComparer, il suo stato non è solo una mappatura tra le chiavi effettivamente contenute e i loro valori associati, ma invece una mappatura tra i set di equivalenza definiti dalle chiavi e dal comparatore e i loro valori associati. Se due istanze d1e d2di Dictionaryriferimenti tenere premuto per lo stesso IEqualityComparer, sarà possibile produrre una nuova istanza d3tale che per ogni x, d3.ContainsKey(x)sarà uguale d1.ContainsKey(x) || d2.ContainsKey(x). Se, tuttavia, d1e d2può contenere riferimenti a diversi comparatori di uguaglianza arbitraria, in generale non ci sarà modo di unirli in modo da garantire che la condizione sia valida.

L'aggiunta di variabili di istanza nel tentativo di eliminare i singleton, ma non riuscire a tenere adeguatamente conto dei possibili nuovi stati che potrebbero essere implicati da tali variabili di istanza può creare codice che finisce per essere più fragile di quanto sarebbe stato con un singleton. Se ogni istanza di oggetto ha un campo separato, ma le cose non funzioneranno correttamente a meno che non identifichino tutte la stessa istanza di oggetto, allora tutti i nuovi campi acquistati sono un numero maggiore di modi in cui le cose possono andare storte.


1
Sebbene questo sia sicuramente un aspetto interessante della discussione, non penso che in realtà affronti la domanda effettivamente posta dal PO.
Josh

1
@JoshPetrie: I commenti sul post originale (ad es. Di Magus) suggeriscono che il costo di runtime di portare in giro i riferimenti per evitare di usare i singoli non era eccezionale. In quanto tale, considererei rilevanti i costi semantici di avere ogni oggetto incapsulare riferimenti allo scopo di evitare i singoli. Si dovrebbero evitare i singoli punti nei casi in cui possono essere evitati a buon mercato e facilmente, ma il codice che non richiede apertamente un singleton ma potrebbe rompersi non appena esistono più istanze di qualcosa è peggio del codice che usa un singleton.
supercat

2
Le risposte non servono per rispondere ai commenti, ma per rispondere direttamente alla domanda.
ClassicThunder

@ClassicThunder: ti piace di più la modifica?
supercat
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.