Alternativen zu Singletons / Globals


16

Ich habe unzählige Male von den Fallstricken der Singletons / Globals gehört und verstehe, warum sie so oft verpönt werden.

Was ich nicht verstehe, ist, was die elegante, unordentliche Alternative ist. Es scheint, dass die Alternative zur Verwendung von Singletons / Globals immer darin besteht, Objekte eine Million Ebenen tiefer durch Ihre Engine-Objekte zu leiten, bis sie die Objekte erreichen, die sie benötigen.

Zum Beispiel lade ich in meinem Spiel einige Assets vor, wenn das Spiel gestartet wird. Diese Assets werden erst viel später verwendet, wenn der Spieler durch das Hauptmenü navigiert und das Spiel betritt. Soll ich diese Daten von meinem Game-Objekt an mein ScreenManager-Objekt übergeben (obwohl nur ein Bildschirm diese Daten tatsächlich berücksichtigt), dann an das entsprechende Screen-Objekt und an einen anderen Ort?

Es scheint nur, dass ich globale Zustandsdaten gegen überfüllte Abhängigkeitsinjektionen eintausche und Daten an Objekte weitergebe, die sich nicht einmal um die Daten kümmern, außer um sie an untergeordnete Objekte weiterzugeben.

Ist dies ein Fall, in dem ein Singleton eine gute Sache wäre, oder gibt es eine elegante Lösung, die ich vermisse?

Antworten:


16

Kombiniere keine Singletons und Globals. Während normalerweise eine Art globaler Variablen erforderlich ist, ist der Singleton nicht nur ein Ersatz für eine globale Variable, sondern in erster Linie eine Möglichkeit, Probleme der statischen Initialisierungsreihenfolge in C ++ ( und FQA ) zu umgehen . (In anderen Sprachen können so verschiedene Sprachmängel wie das Fehlen globaler Variablen und einfacher Funktionen umgangen werden.)

Wenn Sie nur einen globalen Zeiger anstelle eines Singletons verwenden und sicherstellen, dass er (manuell) initialisiert wird, bevor etwas benötigt wird, vermeiden Sie den Funktionsaufruf und den Verzweigungsaufwand, die lahme Syntax, um auf das Objekt zuzugreifen, und Sie können tatsächlich einen erstellen zweite Instanz der Klasse, wenn Sie dies für Tests benötigen oder weil sich Ihr Design geändert hat.

Für die wenigen gewünschten globalen Variablen (gängige Beispiele sind Audioausgabe, Liste der geöffneten Fenster, Tastaturbehandlungsroutine usw.) empfehle ich das Service-Locator-Muster . Es macht es einfach, Dinge durch verschiedene Implementierungen zu ersetzen (z. B. Real- oder Null-Audiogerät), und sammelt alle Ihre globalen Daten in einer Struktur, um eine Verschmutzung Ihres Namespaces zu vermeiden.


+1. Gute Antwort und vielen Dank für den Link zum Servicelokalisierungsmuster. Das ist eine sehr interessante Lektüre.
Mistzack

1

Wenn Sie nicht möchten, dass ein Teil des Codes einige Daten magisch "kennt", muss er irgendwie übergeben werden. Das heißt aber nicht, dass es nur durch Argumente weitergegeben werden muss.

Könnten Sie in Ihrem Beispiel nicht eine Art "AssetManager" haben, der die Assets lädt und speichert, und dann müsste dem ScreenManager nur ein Verweis darauf gegeben werden (wahrscheinlich bei der Erstellung)? In diesem Sinne übergeben Sie die Verweise auf die Assets, die in einem anderen Objekt eingeschlossen sind, und Sie können diese bei der Initialisierung einmal übergeben, anstatt sie bei Bedarf an die Blattfunktion weiterzugeben.

Nun, IMHO, dass AssetManager, die Art von Dingen, von denen Sie nur eines wollen, genauso gut ein Singleton sein könnte. Vorausgesetzt, Sie verstehen die Fallstricke und codieren sie speziell, um sie zu vermeiden (nehmen Sie an, dass auf Singleton gleichzeitig von mehreren Threads aus zugegriffen wird, und stechen Sie sich jedes Mal mit einer Gabel, wenn Sie etwas tun, das blockiert werden muss), und hauen Sie sich dann aus.


1

Ich denke Jason D hat absolut recht - so würde ich damit umgehen:

Das Spiel verfügt über eine Instanz von AssetManager, einem Objekt, von dem Sie ein beliebiges Asset nach Namen abrufen können.

Im Spiel:

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

Im ScreenManager:

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

Auf dem Bildschirm:

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

Jetzt haben alle Bildschirme Zugriff auf alle benötigten Assets. Dies ist nicht komplexer oder verrückter als die Verwendung von Globals oder Singletons, und Sie haben die Möglichkeit, zwei Instanzen von Game in derselben Anwendung ohne Konflikte auszuführen. Ich musste einmal ein Spiel machen, das aus 8 Minispielen bestand, die alle die gleichen Basisklassen / Frameworks hatten. Ich musste alle meine Globals / Singletons überarbeiten, um diesen Referenz-Passing-Stil zu verwenden, und ich habe nie zurückgeschaut. Die einzigen Dinge, die global sein sollten, sind Dinge, die nur einmal physisch existieren können, wie Audio, Networking, I / O usw.


0

Sie können das Factory-Muster verwenden, um Singleton zu ersetzen . Dann hat die Factory-Klasse die Kontrolle darüber, wie viele Instanzen Sie erstellen können, die Sie später leicht ändern können, wenn Sie feststellen, dass Sie mehr als eine benötigen AssetManager. Wie in diesem Artikel angegeben :

Es gibt Ihnen die Flexibilität des Singleton, mit bei weitem nicht so vielen Problemen.


Eine andere, eher eingeschränkte Möglichkeit besteht darin, die Klasse statisch zu machen (was meines Erachtens für einen AssetManager nicht machbar ist und nur in Sprachen möglich ist, die überhaupt statische Klassen haben). Dies funktioniert jedoch nur, wenn Sie keine Vererbung / keinen Polymorphismus benötigen. Es ist eine sehr unflexible Lösung:

statische Methoden sind so flexibel wie Granit. Jedes Mal, wenn Sie eines verwenden, setzen Sie einen Teil Ihres Programms in Beton um. Stellen Sie nur sicher, dass Ihr Fuß nicht eingeklemmt ist, während Sie zusehen, wie er hart wird. Eines Tages werden Sie erstaunt sein, dass Sie wirklich eine weitere Implementierung dieser verdammten PrintSpooler-Klasse benötigen, und es hätte eine Schnittstelle, eine Factory und eine Reihe von Implementierungsklassen sein müssen. D'oh!

Hierbei handelt es sich um statische Methoden, sie können jedoch auch auf statische Klassen angewendet werden.


Was meinst du mit "die Klasse statisch machen"? C ++ hat keine statischen Klassen. Meinst du nur statische Methoden? Warum sollte man sich dann die Mühe machen, eine Klasse anstelle eines Namespaces zu haben?

1
@ Joe: Nun, die Frage konzentriert sich nicht auf C ++, wie ich es verstanden habe. In C # oder Java können Sie statische Klassen erstellen, auf die ich verweise. Wie ich bereits sagte, sind statische Klassen die meiste Zeit keine optimale Lösung, aber in seltenen Fällen können sie wie globale Klassen funktionieren.
Michael Klement
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.