Wie kann ich vermeiden, dass meine Spielarchitektur viele Singletons enthält?


55

Ich benutze die cocos2d-x Game Engine zum Erstellen von Spielen. Der Motor verbraucht bereits viele Singletons. Wenn jemand es benutzt hat, sollte er mit einigen von ihnen vertraut sein:

Director
SimpleAudioEngine
SpriteFrameCache
TextureCache
EventDispatcher (was)
ArmatureDataManager
FileUtils
UserDefault

und vieles mehr mit insgesamt ca. 16 Klassen. Eine ähnliche Liste finden Sie auf dieser Seite: Singleton-Objekte in Cocos2d-html5 v3.0 Aber wenn ich ein Spiel schreiben will, brauche ich viel mehr Singletons:

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)
.....

Warum denke ich, dass sie Singletons sein sollten? Weil ich sie an sehr unterschiedlichen Orten in meinem Spiel brauche und ein gemeinsamer Zugriff sehr praktisch wäre. Mit anderen Worten, ich weiß nicht, was ich tun soll, um sie irgendwo zu erstellen, und gebe Zeiger an meine gesamte Architektur weiter, da dies sehr schwierig sein wird. Auch das sind Dinge, die ich nur einmal brauche. In jedem Fall brauche ich mehrere, ich kann auch ein Multiton-Muster verwenden. Das Schlimmste ist jedoch, dass Singleton das am häufigsten kritisierte Muster ist, weil:

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

Hier finden Sie einige Gedanken: https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons und https://stackoverflow.com/questions/4074154/when-should-the-singleton -muster-nicht-verwendet-werden-neben-dem-offensichtlichen

Also, ich denke, ich mache etwas falsch. Ich denke, mein Code riecht . :) Ich wundere mich, wie mehr erfahrene Spieleentwickler dieses Architekturproblem lösen? Ich möchte überprüfen, ob es in der Spieleentwicklung noch normal ist, mehr als 30 Singletons zu haben, die bereits in der Spiele-Engine enthalten sind.

Ich habe mir überlegt, eine Singleton-Fassade zu verwenden, die Instanzen all dieser Klassen enthält, die ich benötige, aber nicht jede von ihnen wäre bereits ein Singleton. Dadurch werden viele Probleme beseitigt, und ich hätte nur einen einzigen Singleton, der die Fassade selbst wäre. Aber in diesem Fall werde ich ein anderes Designproblem haben. Die Fassade wird zu einem GOTTESOBJEKT. Ich denke, das riecht auch . Daher kann ich für diese Situation keine gute Designlösung finden. Bitte um Rat.


26
Die meisten Leute, die gegen Singleton argumentieren, scheinen nicht zu begreifen, dass die Arbeit, die erforderlich ist, um einige Daten / Funktionen durch den gesamten Code zu verbreiten, eher eine Fehlerquelle als die Verwendung eines Singleton sein könnte. Sie sind also im Grunde genommen keine Vergleiche, sondern lehnen einfach ab ==> Ich vermute, Anti-Singletonismus ist eine Haltung :-) Nun, Singleton hat Fehler. Wenn Sie dies vermeiden können, tun Sie dies, um die damit verbundenen Probleme zu vermeiden Wenn Sie es nicht können, versuchen Sie es ohne Rückblick. Immerhin scheint Cocos2d mit seinen 16 Singletons ganz gut zu funktionieren ...
GameAlchemist

6
Zur Erinnerung, dies ist eine Frage zum Ausführen einer bestimmten Aufgabe. Dies ist keine Frage, die nach einer Pro / Contra-Diskussion über Singletons verlangt (die hier sowieso nicht zum Thema gehören würde). Denken Sie außerdem daran, dass Kommentare verwendet werden sollten, um den Fragesteller um Klärung zu bitten, und nicht als Plattform für eine tangentiale Diskussion über die Vor- und Nachteile von Singletons. Wenn Sie Ihre Eingabe bereitstellen möchten, müssen Sie eine legitime Antwort auf die Frage geben , in der Sie Ihre Lösung mit Ratschlägen für oder gegen das Singleton-Muster verfeinern können.
Josh

Ich gehe davon aus, dass diese Singletons dann global erreichbar sind? Welches wäre ein Grund, dieses Muster zu vermeiden.
Peter - Wiedereinsetzung von Monica

Antworten:


41

Ich initialisiere meine Services in meiner Hauptanwendungsklasse und übergebe sie dann als Zeiger auf die Anforderungen, die zur Verwendung durch die Konstruktoren oder Funktionen erforderlich sind. Dies ist aus zwei Gründen nützlich.

Erstens ist die Reihenfolge der Initialisierung und Bereinigung einfach und klar. Es gibt keine Möglichkeit, einen Dienst versehentlich an einem anderen Ort zu initialisieren, wie dies mit einem Singleton möglich ist.

Zweitens ist klar, wer von was abhängt. Ich kann einfach anhand der Klassendeklaration erkennen, welche Klassen welche Dienste benötigen.

Sie mögen denken, es ist ärgerlich, all diese Objekte herumzugeben, aber wenn das System gut entworfen ist, ist es überhaupt nicht schlecht und macht die Dinge viel klarer (für mich jedenfalls).


34
+1. Das "Ärgernis", Verweise auf Systeme weitergeben zu müssen, hilft außerdem, dem Abhängigkeitsschleichen entgegenzuwirken. Wenn Sie etwas an " alles " übergeben müssen, ist dies ein gutes Zeichen dafür, dass Sie ein schlechtes Kopplungsproblem in Ihrem Design haben, und dies ist auf diese Weise viel eindeutiger als beim promiskuitiven globalen Zugriff auf alles von überall.
Josh

2
Dies bietet eigentlich keine Lösung ... Sie haben also Ihre Programmklasse mit einer Reihe von Singleton-Objekten darin und jetzt benötigen 10 Ebenen tief im Code irgendetwas in der Szene eines dieser Singletons ... wie wird es weitergegeben?
Krieg

Wardy: Warum sollten sie Singletons sein? Sicher, meistens übergeben Sie eine bestimmte Instanz, aber was einen Singleton zu einem Singleton macht, ist, dass Sie KEINE andere Instanz verwenden KÖNNEN. Mit der Injektion können Sie eine Instanz für ein Objekt und eine andere für das nächste Objekt übergeben. Nur weil Sie das aus irgendeinem Grund nicht tun, heißt das noch lange nicht, dass Sie es nicht können.
Parar

3
Sie sind keine Singletons. Sie sind reguläre Objekte wie alles andere mit einem genau definierten Eigentümer, der init / cleanup steuert. Wenn Sie 10 Ebenen tief sind und keinen Zugriff haben, liegt möglicherweise ein Entwurfsproblem vor. Nicht alles muss oder sollte Zugriff auf Renderer, Audio und dergleichen haben. Auf diese Weise denken Sie über Ihr Design nach. Als zusätzlichen Bonus für diejenigen, die ihren Code testen (was ??), wird das Testen von Einheiten auch viel einfacher.
1.

2
Ja, ich denke, dass die Abhängigkeitsinjektion (mit oder ohne Framework wie Spring) eine perfekte Lösung ist, um damit umzugehen. Ich fand, dass dies ein großartiger Artikel für diejenigen ist, die sich fragen, wie Code besser strukturiert werden kann, um zu vermeiden, dass Dinge überall weitergegeben werden: misko.hevery.com/2008/10/21/…
megadan

17

Ich werde nicht über das Böse hinter Singletons diskutieren, weil das Internet das besser kann als ich.

In meinen Spielen verwende ich das Service Locator-Muster , um Tonnen von Singletons / Managern zu vermeiden.

Das Konzept ist ziemlich einfach. Sie haben nur einen Singleton, der als einzige Schnittstelle fungiert, um das zu erreichen, was Sie als Singleton verwendet haben. Anstatt mehrere Singleton zu haben, haben Sie jetzt nur noch einen.

Anrufe wie:

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

So sieht es mit dem Service Locator-Muster aus:

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

Wie Sie sehen, ist jeder Singleton im Service Locator enthalten.

Um eine detailliertere Erklärung zu erhalten, empfehle ich Ihnen, diese Seite über die Verwendung des Locator-Musters zu lesen .

[ BEARBEITEN ]: Wie in den Kommentaren erwähnt, enthält die Site gameprogrammingpatterns.com auch einen sehr interessanten Abschnitt über Singleton .

Ich hoffe, es hilft.


4
Die hier verlinkte Seite gameprogrammingpatterns.com enthält auch ein Kapitel über Singletons , das relevant zu sein scheint.
Ajp15243

28
Service Locators sind nur Singletons, die alle normalen Probleme zur Laufzeit verschieben und nicht zur Kompilierungszeit. Was Sie jedoch vorschlagen, ist nur eine riesige statische Registrierung, die fast besser ist, aber immer noch im Wesentlichen eine Masse globaler Variablen. Dies ist einfach eine Frage der schlechten Architektur, wenn viele Ebenen Zugriff auf dieselben Objekte benötigen. Hier ist die richtige Abhängigkeitsinjektion erforderlich, nicht anders benannte Globals als die aktuellen Globals.
Magus

2
Können Sie die "richtige Abhängigkeitsinjektion" näher erläutern?
Blue Wizard

17
Das hilft dem Problem nicht wirklich. Es sind im Wesentlichen viele Singletons, die zu einem riesigen Singleton verschmolzen sind. Keines der zugrundeliegenden strukturellen Probleme ist behoben oder gar behoben, der Code ist immer noch hübscher.
ClassicThunder

4
@classicthunder Obwohl dies nicht jedes einzelne Problem angeht, ist dies eine große Verbesserung gegenüber Singletons, da es mehrere Probleme angeht. Insbesondere ist der von Ihnen geschriebene Code nicht mehr an eine einzelne Klasse gekoppelt, sondern an eine Schnittstelle / Kategorie von Klassen. Jetzt können Sie problemlos verschiedene Implementierungen der verschiedenen Dienste austauschen.
jhocking

12

Beim Lesen all dieser Antworten, Kommentare und Artikel wurde darauf hingewiesen, insbesondere auf diese beiden brillanten Artikel.

Schließlich bin ich zu folgendem Schluss gekommen, was eine Art Antwort auf meine eigene Frage ist. Der beste Ansatz ist, nicht faul zu sein und Abhängigkeiten direkt zu übergeben. Dies ist explizit, überprüfbar, es gibt keinen globalen Zugriff, kann auf ein falsches Design hinweisen, wenn Sie die Delegierten sehr tief und an vielen Stellen übergeben müssen und so weiter. Aber beim Programmieren passt eine Größe nicht für alle. Daher gibt es immer noch Fälle, in denen es nicht praktisch ist, sie direkt zu übergeben. Zum Beispiel für den Fall, dass wir ein Spiel-Framework erstellen (eine Codevorlage, die als Basiscode für die Entwicklung verschiedener Arten von Spielen wiederverwendet wird) und dort viele Dienste enthalten. Hier wissen wir nicht, wie tief jeder Service übergeben werden kann und an wie vielen Stellen er benötigt wird. Es hängt von einem bestimmten Spiel ab. Was machen wir also in solchen Fällen? Wir verwenden das Service Locator-Entwurfsmuster anstelle von Singleton.

  1. Sie programmieren nicht auf die Implementierungen, sondern auf die Schnittstellen. Dies ist in Bezug auf OOP-Prinzipien sehr wichtig. Zur Laufzeit können Sie den Dienst mithilfe des Null-Objektmusters ändern oder sogar deaktivieren. Dies ist nicht nur für die Flexibilität wichtig, sondern hilft auch direkt beim Testen. Sie können deaktivieren, verspotten und das Decorator-Muster verwenden, um Protokollierung und andere Funktionen hinzuzufügen. Dies ist eine sehr wichtige Eigenschaft, die Singleton nicht besitzt.
  2. Ein weiterer großer Vorteil ist, dass Sie die Lebensdauer des Objekts steuern können. In Singleton steuern Sie nur die Zeit, zu der das Objekt durch das Lazy Initialization-Muster erzeugt wird. Sobald das Objekt erzeugt wurde, bleibt es bis zum Ende des Programms erhalten. In einigen Fällen möchten Sie jedoch den Service ändern. Oder sogar herunterfahren. Gerade beim Spielen ist diese Flexibilität sehr wichtig.
  3. Es kombiniert / verpackt mehrere Singletons in einer kompakten API. Ich denke, Sie können auch einige Facade-Locators wie Service Locators verwenden, bei denen für eine einzelne Operation möglicherweise mehrere Services gleichzeitig verwendet werden.
  4. Sie können argumentieren, dass wir immer noch den globalen Zugriff haben, der das Hauptproblem beim Design des Singleton ist. Ich stimme zu, aber wir werden dieses Muster nur brauchen und nur, wenn wir einen globalen Zugang wollen. Wir sollten uns nicht über den globalen Zugriff beschweren, wenn wir der Meinung sind, dass eine direkte Abhängigkeitsinjektion für unsere Situation nicht praktisch ist und wir einen globalen Zugriff benötigen. Aber auch für dieses Problem ist eine Verbesserung verfügbar (siehe den zweiten Artikel oben). Sie können alle Dienste als privat definieren und von der Service Locator-Klasse alle Klassen erben, von denen Sie glauben, dass sie die Dienste verwenden sollten. Dies kann den Zugang erheblich einschränken. Bitte beachten Sie, dass Sie im Falle von Singleton die Vererbung aufgrund des privaten Konstruktors nicht verwenden können.

Wir sollten auch berücksichtigen, dass dieses Muster in Unity, LibGDX, XNA verwendet wurde. Dies ist kein Vorteil, aber dies ist eine Art Beweis für die Verwendbarkeit des Musters. Bedenken Sie, dass diese Motoren lange Zeit von vielen intelligenten Entwicklern entwickelt wurden und in den Entwicklungsphasen des Motors noch keine bessere Lösung gefunden haben.

Abschließend denke ich, dass Service Locator für die in meiner Frage beschriebenen Szenarien sehr nützlich sein kann, aber dennoch vermieden werden sollte, wenn es möglich ist. Als Faustregel - verwenden Sie es, wenn Sie müssen.

EDIT: Nach einem Jahr der Arbeit mit Direct DI und Problemen mit riesigen Konstruktoren und vielen Delegationen habe ich mehr Nachforschungen angestellt und einen guten Artikel gefunden, der über Vor- und Nachteile von DI- und Service Locator-Methoden spricht. Zitiert wird dieser Artikel ( http://www.martinfowler.com/articles/injection.html ) von Martin Fowler, der erklärt, wann die Service-Locators tatsächlich schlecht sind:

Das Hauptproblem betrifft also Personen, die Code schreiben, der voraussichtlich in Anwendungen außerhalb der Kontrolle des Autors verwendet wird. In diesen Fällen ist sogar eine minimale Annahme über einen Service Locator ein Problem.

Aber wie auch immer, wenn Sie uns DI zum ersten Mal möchten, sollten Sie diesen http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/ Artikel lesen (darauf hingewiesen von @megadan) durch sehr sorgfältige Beachtung des Gesetzes von Demeter (LoD) oder des Grundsatzes des geringsten Wissens .


Meine persönliche Unverträglichkeit gegenüber Service Locators beruht hauptsächlich auf meinen Versuchen, Code zu testen, der sie verwendet. Im Interesse von Black-Box-Tests rufen Sie den Konstruktor auf, um eine zu testende Instanz zu erstellen. Eine Ausnahme kann sofort oder bei einem Methodenaufruf ausgelöst werden, und es gibt möglicherweise keine öffentlichen Eigenschaften, die Sie zu den fehlenden Abhängigkeiten führen. Dies kann geringfügig sein, wenn Sie Zugriff auf die Quelle haben, verstößt aber dennoch gegen das Prinzip der geringsten Überraschung (ein häufiges Problem bei Singletons). Sie haben vorgeschlagen, den Service-Locator einzureichen, der einen Teil davon erleichtert, und sind dafür zu loben.
Magus

3

Es ist nicht ungewöhnlich, dass Teile einer Codebasis als Eckpfeiler oder als Basisklasse betrachtet werden, aber das rechtfertigt nicht, dass der Lebenszyklus als Singleton diktiert wird.

Programmierer verlassen sich oft auf das Singleton-Muster als Mittel der Bequemlichkeit und puren Faulheit, anstatt sich auf eine alternative Herangehensweise zu beschränken und ein bisschen wortreicher zu sein und in einem expliziten Stil Objektbeziehungen zwischen einander zu erzwingen.

Fragen Sie sich also, was einfacher zu warten ist.

Wenn Sie wie ich sind, ist es mir lieber, eine Header-Datei zu öffnen und Konstruktorargumente und öffentliche Methoden kurz zu überfliegen, um zu sehen, welche Abhängigkeiten ein Objekt benötigt, anstatt Hunderte von Codezeilen in einer Implementierungsdatei untersuchen zu müssen.

Wenn Sie ein Objekt zu einem Singleton gemacht und später erkannt haben, dass Sie in der Lage sein müssen, mehrere Instanzen des Objekts zu unterstützen, stellen Sie sich die umfassenden Änderungen vor, die erforderlich sind, wenn Sie diese Klasse in Ihrer gesamten Codebasis ziemlich häufig verwendet haben. Die bessere Alternative besteht darin, Abhängigkeiten explizit anzugeben, auch wenn das Objekt nur einmal zugeordnet wird und wenn die Notwendigkeit besteht, mehrere Instanzen zu unterstützen, können Sie dies ohne Bedenken tun.

Wie bereits erwähnt, verschleiert die Verwendung des Singleton-Musters auch die Kopplung, was häufig auf ein schlechtes Design und eine hohe Kohäsion zwischen den Modulen hinweist. Eine solche Kohäsion ist normalerweise unerwünscht und führt zu sprödem Code, der sich nur schwer aufrechterhalten lässt.


-1: Diese Antwort beschreibt das Singleton-Muster falsch, und alle Argumente beschreiben die Probleme mit der schlechten Implementierung des Musters. Richtiger Singleton ist eine Schnittstelle und vermeidet jedes einzelne von Ihnen beschriebene Problem (einschließlich der Verlagerung auf Nicht-Singleness, ein Szenario, das von GoF explizit angesprochen wird). Wenn es schwierig ist, die Verwendung eines Singletons umzugestalten, kann die Umgestaltung der Verwendung einer expliziten Objektreferenz nur schwieriger sein, nicht weniger. Wenn Singleton irgendwie MEHR Code-Kopplung verursacht, wurde es einfach falsch gemacht.
Seth Battin

1

Singleton ist ein berühmtes Muster, aber es ist gut zu wissen, welchen Zwecken es dient und welche Vor- und Nachteile es hat.

Es macht wirklich Sinn, wenn überhaupt keine Beziehung besteht .
Wenn Sie mit Ihrer Komponente mit einem völlig anderen Objekt umgehen können (keine starke Abhängigkeit) und dasselbe Verhalten erwarten, ist der Singleton möglicherweise eine gute Wahl.

Wenn Sie andererseits eine kleine Funktionalität benötigen , die nur zu bestimmten Zeiten verwendet wird, sollten Sie es vorziehen , Referenzen zu übergeben .

Wie Sie bereits erwähnt haben Singletons keine lebenslange Kontrolle. Aber der Vorteil ist, dass sie eine verzögerte Initialisierung haben.
Wenn Sie diesen Singleton in Ihrer Anwendungslebensdauer nicht aufrufen, wird er nicht instanziiert. Aber ich bezweifle, dass Sie Singletons bauen und sie nicht verwenden.

Sie müssen einen Singleton wie einen Dienst anstelle einer Komponente sehen .

Singletons funktioniert, sie haben nie eine Anwendung unterbrochen. Aber ihre Mängel (die vier, die du angegeben hast) sind Gründe, warum du keinen machst.


1

Der Motor verbraucht bereits viele Singletons. Wenn jemand es benutzt hat, sollte er mit einigen von ihnen vertraut sein:

Unbekanntheit ist nicht der Grund, warum Singletons vermieden werden müssen.

Es gibt viele gute Gründe, warum Singleton notwendig oder unvermeidbar ist. Spiel-Frameworks verwenden häufig Singletons, da dies eine unvermeidliche Folge der Verwendung einer einzigen Stateful-Hardware ist. Es macht keinen Sinn, diese Hardware jemals mit mehreren Instanzen ihrer jeweiligen Handler steuern zu wollen. Die Grafikoberfläche ist eine externe Stateful-Hardware, und das versehentliche Initialisieren einer zweiten Kopie des Grafik-Subsystems ist geradezu katastrophal, da sich die beiden Grafik-Subsysteme jetzt gegenseitig darum streiten, wer wann zeichnen darf und sich unkontrolliert überschreiben. Ähnlich wie beim Ereigniswarteschlangensystem streiten sie sich darum, wer die Mausereignisse auf nicht deterministische Weise abruft. Wenn es sich um eine zustandsbehaftete externe Hardware handelt, in der es nur eine gibt, ist Singleton unvermeidlich, um Konflikte zu vermeiden.

Ein weiterer Ort, an dem Singleton sinnvoll ist, sind Cache-Manager. Caches sind ein Sonderfall. Sie sollten nicht wirklich als Singleton betrachtet werden, selbst wenn sie dieselben Techniken wie Singletons anwenden, um am Leben zu bleiben und für immer zu leben. Caching-Dienste sind transparente Dienste. Sie sollen das Verhalten des Programms nicht ändern. Wenn Sie also den Cache-Dienst durch einen Null-Cache ersetzen, sollte das Programm weiterhin funktionieren, außer dass es nur langsamer ausgeführt wird. Der Hauptgrund, warum Cache-Manager eine Ausnahme von Singleton darstellen, ist, dass es nicht sinnvoll ist, einen Cache-Dienst vor dem Herunterfahren der Anwendung selbst herunterzufahren, da dadurch auch die zwischengespeicherten Objekte verworfen werden, was den Punkt zunichte macht, dass es sich um einen handelt Singleton.

Das sind gute Gründe, warum diese Dienste Singletons sind. Keiner der guten Gründe, Singletons zu haben, trifft jedoch auf die von Ihnen aufgelisteten Klassen zu.

Weil ich sie an sehr unterschiedlichen Orten in meinem Spiel brauche und ein gemeinsamer Zugriff sehr praktisch wäre.

Das sind keine Gründe für Singletons. Das sind Gründe für Globals. Dies ist auch ein Zeichen für schlechtes Design, wenn Sie viele Dinge in verschiedenen Systemen austauschen müssen. Das Weitergeben von Dingen weist auf eine hohe Kopplung hin, die durch ein gutes OO-Design verhindert werden kann.

Wenn Sie sich nur die Liste der Klassen in Ihrem Beitrag ansehen, von denen Sie glauben, dass sie Singletons sein müssen, kann ich sagen, dass die Hälfte davon eigentlich keine Singletons sein sollte und die andere Hälfte den Anschein hat, dass sie gar nicht erst da sein sollten. Dass Sie Objekte weitergeben müssen, liegt eher an der fehlenden Kapselung als an den tatsächlichen guten Anwendungsfällen für Singletons.

Lassen Sie uns Ihre Klassen Stück für Stück betrachten:


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

Nur von den Klassennamen würde ich sagen, dass diese Klassen nach anämischem Domänenmodell- Antipattern riechen. Anämische Objekte führen normalerweise dazu, dass viele Daten weitergegeben werden müssen, was die Kopplung erhöht und den Rest des Codes umständlich macht. Außerdem verbirgt die anämische Klasse die Tatsache, dass Sie wahrscheinlich immer noch prozedural denken, anstatt die Objektorientierung zum Einkapseln von Details zu verwenden.


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

Warum müssen diese Klassen Singletons sein? Es scheint mir, dass dies kurzlebige Klassen sein sollten, die aufgerufen werden sollten, wenn sie benötigt werden, und dekonstruiert werden sollten, wenn der Benutzer den Kauf abschließt oder wenn die Anzeigen nicht mehr gezeigt werden müssen.


EntityComponentSystemManager (mananges entity creation and manipulation)

Mit anderen Worten, ein Konstruktor und eine Serviceschicht? Warum gibt es in dieser Klasse überhaupt keine klaren Grenzen und keinen klaren Zweck?


PlayerProgress (passed levels, stars)

Warum ist der Spielerfortschritt von der Spielerklasse getrennt? Die Player-Klasse sollte wissen, wie sie ihren eigenen Fortschritt verfolgt. Wenn Sie die Fortschrittsverfolgung in einer anderen Klasse als der Player-Klasse implementieren möchten, um die Verantwortung zu trennen, sollte PlayerProgress hinter Player stehen.


Box2dManager (manages the physics world)

Ich kann nicht weiter darauf eingehen, ohne zu wissen, was diese Klasse tatsächlich tut, aber eins ist klar: Diese Klasse hat einen schlechten Namen.


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

Dies scheinen die einzigen Klassen zu sein, in denen Singleton sinnvoll sein kann, da die sozialen Verbindungsobjekte eigentlich nicht Ihre Objekte sind. Die sozialen Verbindungen sind nur ein Schatten von externen Objekten, die in einem entfernten Dienst leben. Mit anderen Worten, dies ist eine Art Cache. Stellen Sie nur sicher, dass Sie den Caching-Teil klar vom Service-Teil des externen Dienstes trennen, da der letztere Teil kein Singleton sein muss.

Eine Möglichkeit, die Weitergabe von Instanzen dieser Klassen zu vermeiden, ist die Verwendung der Nachrichtenübergabe. Anstatt Instanzen dieser Klassen direkt aufzurufen, senden Sie stattdessen asynchron eine an den Analytic- und SocialConnection-Dienst adressierte Nachricht, und diese Dienste sind abonniert, um diese Nachrichten zu empfangen und auf die Nachricht zu reagieren. Da es sich bei der Ereigniswarteschlange bereits um einen Singleton handelt, müssen Sie bei der Kommunikation mit einem Singleton keine tatsächliche Instanz übergeben, indem Sie den Anruf mit einem Trampolin versehen.


-1

Singletons sind für mich IMMER schlecht.

In meiner Architektur habe ich eine GameServices-Sammlung erstellt, die von der Spielklasse verwaltet wird. Ich kann nach Bedarf suchen, um dieser Sammlung etwas hinzuzufügen oder daraus zu entfernen.

Wenn Sie sagen, dass etwas in globaler Reichweite ist, sagen Sie in den Beispielen, die Sie oben angegeben haben, im Grunde, dass "dieses Ding Gott ist und keinen Meister hat". Ich würde sagen, dass die meisten davon entweder Helferklassen oder GameServices sind.

Aber das ist nur mein Design in meinem Motor, Ihre Situation kann anders sein.

Direkter ausgedrückt ... Der einzige wirkliche Singleton ist das Spiel, da es jeden Teil des Codes besitzt.

Diese Antwort basiert auf der subjektiven Natur der Frage und basiert rein auf meiner Meinung, sie ist möglicherweise nicht für alle Entwickler da draußen korrekt.

Bearbeiten: Wie gewünscht ...

Ok, das ist aus meinem Kopf, da ich im Moment meinen Code nicht vor mir habe, aber im Grunde genommen ist die Game-Klasse die Wurzel eines jeden Spiels, die wirklich ein Singleton ist ... das muss es sein!

Also habe ich folgendes hinzugefügt ...

class Game
{
   IList<GameService> Services;

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

Jetzt habe ich auch eine Systemklasse (statisch), die alle meine Singletons aus dem gesamten Programm enthält (enthält sehr wenig, Spiel- und Konfigurationsoptionen und das wars auch schon) ...

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; }
}

Und Nutzung ...

Von überall kann ich sowas machen

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

Dieser Ansatz bedeutet, dass mein Spiel Dinge "besitzt", die normalerweise als "Singleton" angesehen werden, und ich kann die Basis-GameService-Klasse beliebig erweitern. Ich habe Schnittstellen wie IUpdateable, nach denen mein Spiel sucht, und rufe alle Dienste auf, die es je nach Bedarf für bestimmte Situationen implementieren.

Ich habe auch Dienste, die andere Dienste für spezifischere Szenarien erben / erweitern.

Beispielsweise ...

Ich habe einen Physikdienst, der meine Physiksimulationen verarbeitet. Abhängig von der Szene kann die Physiksimulation jedoch etwas anderes Verhalten erfordern.


1
Sollten die Spieldienste nicht nur einmal instanziiert werden? In diesem Fall handelt es sich nur um ein Singleton-Muster, das auf Klassenebene nicht erzwungen wird. Dies ist das Schlimmste als bei einem ordnungsgemäß implementierten Singleton.
GameAlchemist

2
@Wardy Ich verstehe nicht genau, was du getan hast, aber es klingt wie die Singleton-Fassade, die ich beschrieben habe, und es sollte ein GOTT-Objekt werden, oder?
Narek

10
Es ist nur ein Singleton-Muster, das auf Spielebene implementiert wird. Der interessanteste Aspekt ist also, dass Sie so tun, als würden Sie Singleton nicht verwenden. Nützlich, wenn Ihr Chef aus der Anti-Pattern-Kirche stammt.
GameAlchemist

2
Ich bin mir nicht sicher, wie sich dies effektiv von der Verwendung von Singletons unterscheidet. Ist es nicht der einzige Unterschied, wie spezifisch Sie auf Ihren einzelnen vorhandenen Dienst dieses Typs zugreifen? Dh var tService = Sys.Game.getService<T>(); tService.Something();anstatt das getServiceTeil zu überspringen und direkt darauf zuzugreifen?
Christian

1
@ Christian: In der Tat ist das genau das, was das Service Locator-Antimuster ist. Anscheinend hält diese Community es immer noch für ein empfohlenes Entwurfsmuster.
Magus

-1

Ein wesentlicher Bestandteil der Singleton-Eliminierung, den Gegner von Singleton häufig nicht berücksichtigen, ist das Hinzufügen zusätzlicher Logik, um den weitaus komplizierteren Zustand zu bewältigen, den Objekte einkapseln, wenn Singletons Instanzwerten weichen. Selbst das Definieren des Zustands eines Objekts nach dem Ersetzen eines Singletons durch eine Instanzvariable kann schwierig sein. Programmierer, die Singletons durch Instanzvariablen ersetzen, sollten jedoch darauf vorbereitet sein, damit umzugehen.

Vergleichen Sie beispielsweise Java HashMapmit .NET Dictionary. Beide sind sich recht ähnlich, haben aber einen wesentlichen Unterschied: Java HashMapist fest programmiert equalsund verwendet hashCode(effektiv einen Singleton-Komparator), während .NET Dictionaryeine Instanz von IEqualityComparer<T>als Konstruktorparameter akzeptieren kann. Die Laufzeitkosten für die Aufbewahrung der Software IEqualityComparersind minimal, die damit verbundene Komplexität jedoch nicht.

Da jede DictionaryInstanz ein kapselt IEqualityComparer, ist ihr Status nicht nur eine Zuordnung zwischen den tatsächlich enthaltenen Schlüsseln und den zugehörigen Werten, sondern eine Zuordnung zwischen den durch die Schlüssel und den Komparator definierten Äquivalenzsätzen und den zugehörigen Werten. Wenn zwei Instanzen d1und d2der DictionaryHalt Verweise auf den gleichen IEqualityComparer, wird es möglich sein , eine neue Instanz zu erzeugen , d3so dass für jede x, d3.ContainsKey(x)wird gleich d1.ContainsKey(x) || d2.ContainsKey(x). Wenn jedoch Verweise auf verschiedene willkürliche Gleichheitsvergleicher vorhanden sind d1und vorhanden sein d2können, gibt es im Allgemeinen keine Möglichkeit, sie zusammenzuführen, um sicherzustellen, dass diese Bedingung erfüllt ist.

Das Hinzufügen von Instanzvariablen, um Singletons zu eliminieren, kann dazu führen, dass Code spröder wird, als dies bei einem Singleton der Fall gewesen wäre, wenn die möglichen neuen Zustände, die durch diese Instanzvariablen impliziert werden könnten, nicht ordnungsgemäß berücksichtigt werden. Wenn jede Objektinstanz über ein separates Feld verfügt, die Funktionen jedoch nur dann fehlerhaft sind, wenn alle dieselbe Objektinstanz identifizieren, können mit den neuen Feldern mehr Probleme auftreten.


1
Obwohl dies definitiv ein interessanter Aspekt der Diskussion ist, denke ich nicht, dass es tatsächlich die vom OP gestellte Frage anspricht.
Josh

1
@JoshPetrie: Die Kommentare zum Originalbeitrag (z. B. von Magus) deuteten darauf hin, dass die Laufzeitkosten für das Mitführen von Referenzen zur Vermeidung der Verwendung von Singletons nicht hoch waren. Als solches würde ich die semantischen Kosten für die Einkapselung von Referenzen für jedes Objekt für relevant halten, um Singletons zu vermeiden. Man sollte Singletons in Fällen vermeiden, in denen sie billig und einfach vermieden werden können, aber Code, der keinen Singleton benötigt, aber möglicherweise kaputt geht, sobald mehrere Instanzen von etwas existieren, ist schlechter als Code, der einen Singleton verwendet.
Supercat

2
Antworten dienen nicht der Adressierung der Kommentare, sondern der direkten Beantwortung der Frage.
ClassicThunder

@ClassicThunder: Magst du die Bearbeitung besser?
Supercat
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.