Ich habe mit einem Problem in einem Java-Projekt über Zirkelverweise gerungen. Ich versuche, eine reale Situation zu modellieren, in der die fraglichen Objekte voneinander abhängig zu sein scheinen und voneinander wissen müssen.
Das Projekt ist ein allgemeines Modell für das Spielen eines Brettspiels. Die Grundklassen sind nicht spezifisch, werden jedoch erweitert, um Besonderheiten von Schach, Backgammon und anderen Spielen zu behandeln. Ich habe dies vor 11 Jahren als Applet mit einem halben Dutzend verschiedener Spiele programmiert, aber das Problem ist, dass es voller Zirkelverweise ist. Ich habe es damals implementiert, indem ich alle miteinander verflochtenen Klassen in eine einzige Quelldatei gestopft habe, aber ich habe die Idee, dass das in Java eine schlechte Form ist. Jetzt möchte ich etwas Ähnliches wie eine Android-App implementieren und die Dinge richtig machen.
Die Klassen sind:
Regelbuch: Ein Objekt, das nach bestimmten Kriterien abgefragt werden kann, z. B. nach dem anfänglichen Layout des Bretts, nach anderen Informationen zum anfänglichen Spielstatus, z eine aktuelle oder vorgeschlagene Vorstandsposition.
Brett: Eine einfache Darstellung eines Spielbretts, das angewiesen werden kann, einen Zug zu reflektieren.
MoveList: Eine Liste von Moves. Dies hat zwei Ziele: eine Auswahl von Zügen, die zu einem bestimmten Zeitpunkt verfügbar sind, oder eine Liste von Zügen, die im Spiel ausgeführt wurden. Es könnte in zwei nahezu identische Klassen unterteilt werden, aber das ist für die von mir gestellte Frage nicht relevant und kann die Sache noch komplizierter machen.
Move: ein einziger Zug. Es enthält alles über den Zug als eine Liste von Atomen: nimm ein Stück von hier, lege es dort ab, entferne ein gefangenes Stück von dort.
Status: Die vollständigen Statusinformationen eines laufenden Spiels. Nicht nur die Board-Position, sondern auch eine MoveList und andere Statusinformationen, z. B. wer jetzt umziehen soll. Im Schach würde man notieren, ob der König und die Türme jedes Spielers bewegt worden sind.
Zirkuläre Verweise gibt es zuhauf, zum Beispiel: Das Regelbuch muss den Spielstatus kennen, um zu bestimmen, welche Züge zu einem bestimmten Zeitpunkt verfügbar sind, aber der Spielstatus muss das Regelbuch nach dem anfänglichen Startlayout und den mit einem Zug einhergehenden Nebenwirkungen abfragen es wird gemacht (zB wer zieht als nächstes).
Ich habe versucht, die neuen Klassen hierarchisch zu organisieren, wobei RuleBook an der Spitze stand, da es über alles Bescheid wissen muss. Dies hat jedoch zur Folge, dass viele Methoden in die RuleBook-Klasse verschoben werden müssen (z. B. Verschieben), wodurch sie monolithisch und nicht besonders repräsentativ für das ist, was ein RuleBook sein sollte.
Was ist also der richtige Weg, um dies zu organisieren? Sollte ich RuleBook in BigClassThatDoesAlmostEverythingInTheGame umwandeln, um Zirkelverweise zu vermeiden, und den Versuch aufgeben, das reale Spiel genau zu modellieren? Oder sollte ich mich an die voneinander abhängigen Klassen halten und den Compiler dazu überreden, sie irgendwie zu kompilieren und dabei mein reales Modell beizubehalten? Oder gibt es eine offensichtlich gültige Struktur, die mir fehlt?
Vielen Dank für jede Hilfe, die Sie geben können!
quelle
RuleBook
z. B. dasState
als Argument nehmen und das gültige zurückgeben würdenMoveList
, dh "hier sind wir jetzt, was kann als nächstes getan werden?"Antworten:
Javas Garbage Collector ist nicht auf Referenzzähltechniken angewiesen. Zirkelverweise verursachen in Java keinerlei Probleme. Die Zeit, die aufgewendet wird, um vollkommen natürliche Zirkelverweise in Java zu entfernen, ist Zeitverschwendung.
Nicht nötig. Wenn Sie nur alle Quelldateien auf einmal kompilieren (z. B.
javac *.java
), löst der Compiler alle Vorwärtsreferenzen problemlos auf.Ja. Es wird erwartet, dass die Anwendungsklassen voneinander abhängig sind. Kompilieren aller Dateien Java - Quellcode , die auf zu demselben Paket gehören einmal ist nicht ein kluger Hack, es ist genau so , wie Java soll an die Arbeit.
quelle
Zugegeben, kreisförmige Abhängigkeiten sind aus gestalterischer Sicht eine fragwürdige Praxis, aber sie sind nicht verboten, und aus rein technischer Sicht sind sie nicht unbedingt problematisch , wie Sie meinen: Sie sind vollkommen legal in In den meisten Szenarien sind sie in bestimmten Situationen unvermeidlich, und in einigen seltenen Fällen können sie sogar als nützlich angesehen werden.
Tatsächlich gibt es sehr wenige Szenarien, in denen der Java-Compiler eine zirkuläre Abhängigkeit verweigert. (Hinweis: Möglicherweise gibt es noch weitere, mir fällt momentan nur Folgendes ein.)
In der Vererbung: Sie können Klasse A nicht erweitern Klasse B, die wiederum Klasse A erweitert, und es ist durchaus vernünftig, dass Sie dies nicht haben können, da die Alternative aus logischer Sicht absolut keinen Sinn ergibt.
Unter methodenlokalen Klassen: Innerhalb einer Methode deklarierte Klassen verweisen möglicherweise nicht zirkulär aufeinander. Dies ist wahrscheinlich nichts anderes als eine Einschränkung des Java-Compilers, möglicherweise, weil die Fähigkeit, so etwas zu tun, nicht nützlich genug ist, um die zusätzliche Komplexität zu rechtfertigen, die in den Compiler gehen müsste, um ihn zu unterstützen. (Die meisten Java-Programmierer sind sich nicht einmal der Tatsache bewusst, dass Sie eine Klasse innerhalb einer Methode deklarieren können, geschweige denn mehrere Klassen deklarieren und diese Klassen dann zirkulär aufeinander verweisen lassen.)
Es ist daher wichtig zu erkennen, dass das Streben nach Minimierung von zirkulären Abhängigkeiten ein Streben nach Designreinheit und nicht nach technischer Korrektheit ist.
Soweit ich weiß, gibt es keinen reduktionistischen Ansatz zur Beseitigung zirkulärer Abhängigkeiten, was bedeutet, dass es kein Rezept gibt, das aus nichts als einfachen, vorher festgelegten, einfachen Schritten besteht, um ein System mit zirkulären Referenzen zu erstellen, diese nacheinander anzuwenden und zu beenden mit einem System ohne Zirkelverweise. Sie müssen sich an die Arbeit machen und Refactoring-Schritte durchführen, die von der Art Ihres Designs abhängen.
In der besonderen Situation, die Sie zur Hand haben, scheint es mir, dass Sie eine neue Entität benötigen, die vielleicht "Spiel" oder "GameLogic" genannt wird und die alle anderen Entitäten kennt (ohne dass es eine der anderen Entitäten weiß, ), damit sich die anderen Entitäten nicht kennen müssen.
Ich halte es zum Beispiel für unvernünftig, dass Ihre RuleBook-Entität irgendetwas über die GameState-Entität wissen muss, da ein Regelbuch etwas ist, das wir zum Spielen heranziehen, es ist nicht etwas, das aktiv am Spielen teilnimmt. Es ist also diese neue "Spiel" -Entität, die sowohl das Regelbuch als auch den Spielstatus konsultieren muss, um zu bestimmen, welche Züge verfügbar sind, und dies beseitigt die zirkulären Abhängigkeiten.
Nun, ich denke, ich kann mir vorstellen, was Ihr Problem mit diesem Ansatz sein wird: Das Codieren der "Spiel" -Entität in einer spielunabhängigen Weise wird sehr schwierig sein, so dass Sie höchstwahrscheinlich nicht nur mit einem, sondern mit zwei enden werden Entitäten, die maßgeschneiderte Implementierungen für jeden Spieltyp benötigen: das "RuleBook" und die "Game" -Entität. Was wiederum den Zweck zunichte macht, eine "RuleBook" -Entität zu haben. Nun, alles, was ich dazu sagen kann, ist, dass Ihr anfängliches Bestreben, ein System zu schreiben, das viele verschiedene Arten von Spielen spielen kann, vielleicht nobel, aber vielleicht schlecht konzipiert war. Wenn ich in Ihren Füßen stünde, hätte ich mich darauf konzentriert, einen gemeinsamen Mechanismus zum Anzeigen des Status aller verschiedenen Spiele und einen gemeinsamen Mechanismus zum Empfangen von Benutzereingaben für alle diese Spiele zu verwenden.
quelle
In der Spieltheorie werden Spiele als Liste vorheriger Züge (Werttypen einschließlich der Spieler) und als Funktion ValidMoves (previousMoves) behandelt.
Ich würde versuchen, diesem Muster für den nicht-UI-Teil des Spiels zu folgen und Dinge wie das Einrichten des Bretts als Moves zu behandeln.
Die Benutzeroberfläche kann dann ein Standard-OO-Zeugs sein, mit einer Möglichkeit, sich auf die Logik zu beziehen
Aktualisieren, um Kommentare zu komprimieren
Betrachten Sie Schach. Schachpartien werden üblicherweise als Zuglisten aufgezeichnet. http://en.wikipedia.org/wiki/Portable_Game_Notation
Die Liste der Züge definiert den vollständigen Zustand des Spiels viel besser als ein Bild des Spielbretts.
Nehmen wir zum Beispiel an, wir fangen an, Objekte für Board, Piece, Move usw. und Methoden wie Piece.GetValidMoves () zu erstellen.
Zuerst sehen wir, dass wir ein Stück Referenz auf die Tafel haben müssen, aber dann überlegen wir, ob wir Rochade spielen sollen. Was Sie nur tun können, wenn Sie Ihren König oder Turm noch nicht bewegt haben. Wir brauchen also eine MovedAlready-Flagge auf dem König und den Türmen. Ebenso können Bauern im ersten Zug 2 Felder ziehen.
Dann sehen wir, dass beim Rochieren der gültige Zug des Königs von der Existenz und dem Zustand des Turms abhängt. Das Board muss also mit Stücken versehen sein und sich auf diese beziehen. Wir befassen uns mit Ihrem Problem mit Rundschreiben.
Wenn wir jedoch Move als unveränderliche Struktur und unveränderlichen Spielstatus definieren, wie in der Liste der vorherigen Züge angegeben, verschwinden diese Probleme. Um zu sehen, ob die Rochade gültig ist, können wir die Zugliste der vorhandenen Burg- und Königszüge überprüfen. Um zu sehen, ob der Bauer en passent sein kann, können wir überprüfen, ob der andere Bauer zuvor einen doppelten Zug ausgeführt hat. Es werden keine Referenzen benötigt, außer Regeln -> Verschieben
Jetzt hat Schach ein statisches Brett, und die Peices werden immer auf die gleiche Weise eingerichtet. Nehmen wir jedoch an, wir haben eine Variante, in der wir eine alternative Einrichtung zulassen. vielleicht einige Stücke als Handicap weglassen.
Wenn wir die Setup-Züge als Züge hinzufügen, 'vom Kästchen zum Quadrat X' und das Rules-Objekt anpassen, um diesen Zug zu verstehen, können wir das Spiel dennoch als eine Folge von Zügen darstellen.
Wenn in Ihrem Spiel das Spielfeld selbst nicht statisch ist, können Sie beispielsweise Schachfelder hinzufügen oder Felder vom Spielfeld entfernen, damit sie nicht verschoben werden können. Diese Änderungen können auch als Verschiebungen dargestellt werden, ohne dass die Gesamtstruktur der Regelengine geändert werden muss oder auf ein BoardSetup- Objekt ähnlicher Art verwiesen werden muss
quelle
boardLayout
ist eine Funktion von allenpriorMoves
(dh wenn wir es als state beibehalten würden, würde nichts anderes als jedes beigetragenthisMove
). Daher ist Ewans Vorschlag im Wesentlichen "cut the middle man" - gültige Bewegungen eine direkte Funktion aller vorherigen, anstattvalidMoves( boardLayout( priorMoves ) )
.Die Standardmethode zum Entfernen eines Zirkelverweises zwischen zwei Klassen in der objektorientierten Programmierung besteht darin, eine Schnittstelle einzuführen, die dann von einer von ihnen implementiert werden kann. In Ihrem Fall könnten Sie also einen
RuleBook
Verweis haben,State
der sich dann auf eineInitialPositionProvider
(von implementierteRuleBook
) Schnittstelle bezieht . Dies erleichtert auch das Testen, da Sie dann eine erstellen können, die zu TestzweckenState
eine andere (vermutlich einfachere) Ausgangsposition verwendet.quelle
Ich glaube, die Zirkelverweise und das Gott-Objekt in Ihrem Fall könnten leicht entfernt werden, indem die Steuerung des Spielflusses von den Staats- und Regelmodellen des Spiels getrennt wird. Auf diese Weise würden Sie wahrscheinlich viel Flexibilität gewinnen und unnötige Komplexität beseitigen.
Ich denke, Sie sollten einen Controller haben ("einen Spielleiter", wenn Sie möchten), der den Spielfluss steuert und aktuelle Statusänderungen handhabt, anstatt dem Regelwerk oder dem Spielstatus diese Verantwortung zu übertragen.
Ein Spielstatusobjekt muss sich nicht selbst ändern oder die Regeln kennen. Die Klasse muss lediglich ein Modell für leicht zu handhabende (erstellte, überprüfte, geänderte, dauerhafte, protokollierte, kopierte, zwischengespeicherte usw.) und effiziente Spielstatusobjekte für den Rest der Anwendung bereitstellen.
Das Regelwerk sollte kein laufendes Spiel kennen oder damit experimentieren müssen. Es sollte nur eine Sicht auf einen Spielstatus erforderlich sein, um feststellen zu können, welche Züge zulässig sind, und es muss nur mit einem resultierenden Spielstatus geantwortet werden, wenn gefragt wird, was passiert, wenn ein Zug auf einen Spielstatus angewendet wird. Es könnte auch einen Anfangszustand des Spiels liefern, wenn nach einem anfänglichen Layout gefragt wird.
Der Controller muss den Spielstatus und das Regelwerk und möglicherweise einige andere Objekte des Spielmodells kennen, aber er sollte nicht mit den Details herumspielen müssen.
quelle
Ich denke, das Problem dabei ist, dass Sie nicht klar beschrieben haben, welche Aufgaben von welchen Klassen zu erledigen sind. Ich werde beschreiben, was meiner Meinung nach eine gute Beschreibung dessen ist, was jede Klasse tun sollte, und dann ein Beispiel für allgemeinen Code geben, der die Ideen veranschaulicht. Wir werden sehen, dass der Code weniger gekoppelt ist und daher keine Zirkelverweise enthält.
Beginnen wir mit der Beschreibung der einzelnen Klassen.
Die
GameState
Klasse sollte nur Informationen über den aktuellen Stand des Spiels enthalten. Es sollte keine Informationen darüber enthalten, in welchem Zustand sich das Spiel in der Vergangenheit befindet oder welche zukünftigen Züge möglich sind. Es sollte nur Informationen darüber enthalten, welche Figuren sich auf welchen Feldern im Schach befinden oder wie viele und welche Arten von Dame sich auf welchen Punkten im Backgammon befinden. DasGameState
muss einige zusätzliche Informationen enthalten, wie zum Beispiel Informationen über das Rochieren im Schach oder über den Verdopplungswürfel im Backgammon.Der
Move
Unterricht ist etwas knifflig. Ich würde sagen, dass ich einen Zug angeben kann, der gespielt werden soll, indem ich festlege, welcher Zug aus dem SpielGameState
resultiert. Sie können sich also vorstellen, dass ein Umzug nur alsGameState
. In go (zum Beispiel) könnte man sich jedoch vorstellen, dass es viel einfacher ist, einen Zug zu bestimmen, indem man einen einzelnen Punkt auf dem Brett angibt. Wir möchten, dass unsereMove
Klasse flexibel genug ist, um beide Fälle zu behandeln. Daher wird dieMove
Klasse tatsächlich eine Schnittstelle mit einer Methode sein, die einen Vorversatz ausführtGameState
und einen neuen Nachversatz zurückgibtGameState
.Jetzt ist die
RuleBook
Klasse dafür verantwortlich, alles über die Regeln zu wissen. Dies kann in drei Dinge unterteilt werden. Es muss wissen, was die InitialeGameState
ist, es muss wissen, welche Züge legal sind, und es muss in der Lage sein zu erkennen, ob einer der Spieler gewonnen hat.Sie könnten auch eine
GameHistory
Klasse bilden, um alle Bewegungen zu verfolgen, die gemacht worden sind und alleGameStates
, die geschehen sind. Eine neue Klasse ist notwendig, weil wir entschieden haben, dass eine einzelneGameState
nicht dafür verantwortlich sein sollte, alleGameState
s zu kennen, die davor standen.Dies schließt die Klassen / Interfaces ab, die ich diskutieren werde. Du hast auch eine
Board
Klasse. Ich denke jedoch, dass Boards in verschiedenen Spielen so unterschiedlich sind, dass es schwer zu erkennen ist, was generell mit Boards gemacht werden kann. Jetzt werde ich generische Interfaces geben und generische Klassen implementieren.Erstens ist
GameState
. Da diese Klasse vollständig vom jeweiligen Spiel abhängig ist, gibt es keine generischeGamestate
Schnittstelle oder Klasse.Weiter ist
Move
. Wie ich bereits sagte, kann dies mit einer Schnittstelle dargestellt werden, die über eine einzige Methode verfügt, die einen Zustand vor dem Verschieben annimmt und einen Zustand nach dem Verschieben erzeugt. Hier ist der Code für diese Schnittstelle:Beachten Sie, dass es einen Typparameter gibt. Dies liegt zum Beispiel daran, dass ein
ChessMove
Wille etwas über die Einzelheiten des Vorzugs wissen mussChessGameState
. So zum Beispiel der Klasse ErklärungChessMove
wäreclass ChessMove extends Move<ChessGameState>
,wo du schon eine
ChessGameState
Klasse definiert hättest .Als nächstes werde ich die generische
RuleBook
Klasse diskutieren . Hier ist der Code:Auch hier gibt es einen Typparameter für die
GameState
Klasse. Da dieRuleBook
wissen soll, wie der Ausgangszustand ist, haben wir eine Methode entwickelt, um den Ausgangszustand anzugeben. DaRuleBook
wissen soll, welche Züge legal sind, haben wir Methoden, um zu testen, ob ein Zug in einem bestimmten Staat legal ist, und um eine Liste der legalen Züge für einen bestimmten Staat zu erstellen. Schließlich gibt es eine Methode zur Auswertung derGameState
. Beachten SieRuleBook
, dass der Spieler nur dafür verantwortlich sein sollte, zu beschreiben, ob der eine oder andere Spieler bereits gewonnen hat, aber nicht, wer sich in der Mitte eines Spiels in einer besseren Position befindet. Zu entscheiden, wer in einer besseren Position ist, ist eine komplizierte Sache, die in eine eigene Klasse verschoben werden sollte. Daher ist dieStateEvaluation
Klasse eigentlich nur eine einfache Aufzählung, die wie folgt lautet:Zuletzt beschreiben wir die
GameHistory
Klasse. Diese Klasse ist dafür verantwortlich, sich alle im Spiel erreichten Positionen sowie die gespielten Züge zu merken. Die Hauptsache, die es tun sollte, ist, ein Video aufzunehmen,Move
wie es abgespielt wurde. Sie können auch Funktionen zum Rückgängigmachen vonMove
s hinzufügen . Ich habe eine Implementierung unten.Schließlich könnten wir uns vorstellen, eine
Game
Klasse zu bilden, um alles zusammenzubinden. DieseGame
Klasse soll Methoden aufzeigen, die es den Leuten ermöglichen, zu sehen, was der StromGameState
ist, zu sehen, wer, wenn jemand einen hat, welche Züge gespielt werden können, und einen Zug zu spielen. Ich habe eine Implementierung untenBeachten Sie in dieser Klasse, dass der
RuleBook
nicht dafür verantwortlich ist, zu wissen, was der StromGameState
ist. Das ist derGameHistory
Job des. DerGame
fragt also nach demGameHistory
aktuellen Stand und gibt diese Information an,RuleBook
wann derGame
Bedarf besteht, zu sagen, was die legalen Schritte sind oder ob jemand gewonnen hat.Der Sinn dieser Antwort ist jedenfalls, dass Sie, sobald Sie eine vernünftige Entscheidung getroffen haben, wofür jede Klasse verantwortlich ist, jede Klasse auf eine kleine Anzahl von Verantwortlichkeiten fokussieren und jede Verantwortung einer eindeutigen Klasse zuweisen, die Klassen neigen dazu, sich zu entkoppeln, und alles wird leicht zu codieren. Hoffentlich geht das aus den Codebeispielen hervor, die ich gegeben habe.
quelle
Zirkuläre Verweise weisen meiner Erfahrung nach im Allgemeinen darauf hin, dass Ihr Design nicht gut durchdacht ist.
In Ihrem Entwurf verstehe ich nicht, warum RuleBook über den Staat "Bescheid wissen" muss. Sicher, es könnte einen Zustand als Parameter für eine Methode erhalten, aber warum sollte es einen Verweis auf einen Zustand kennen müssen (dh als Instanzvariable halten)? Das ergibt für mich keinen Sinn. Ein Regelbuch muss den Status eines bestimmten Spiels nicht "kennen", um seine Aufgabe zu erfüllen. Die Spielregeln ändern sich nicht in Abhängigkeit vom aktuellen Status des Spiels. Entweder haben Sie es falsch entworfen oder Sie haben es richtig entworfen, erklären es aber falsch.
quelle
Die zirkuläre Abhängigkeit ist nicht unbedingt ein technisches Problem, sollte jedoch als Code-Geruch betrachtet werden, der in der Regel eine Verletzung des Single-Responsibility-Prinzips darstellt .
Ihre zirkuläre Abhängigkeit beruht auf der Tatsache, dass Sie versuchen, zu viel aus Ihrem
State
Objekt herauszuholen.Jedes zustandsbehaftete Objekt sollte nur Methoden bereitstellen, die in direktem Zusammenhang mit der Verwaltung dieses lokalen Zustands stehen. Wenn es mehr als die grundlegendste Logik erfordert, sollte es wahrscheinlich in ein größeres Muster aufgeteilt werden. Einige Leute haben unterschiedliche Meinungen dazu, aber als Faustregel gilt: Wenn Sie mehr tun als nur Daten abrufen und einstellen, tun Sie zu viel.
In diesem Fall ist es besser, wenn Sie eine haben
StateFactory
, die etwas über eine wissen könnteRulebook
. Du hättest wahrscheinlich eine andere Controller-Klasse, mit der duStateFactory
ein neues Spiel erstellst.State
sollte auf keinen Fall wissenRulebook
.Rulebook
Möglicherweise wissen Sie von einerState
abhängig von der Implementierung Ihrer Regeln.quelle
Muss ein Regelbuchobjekt an einen bestimmten Spielstatus gebunden sein, oder ist es sinnvoller, ein Regelbuchobjekt mit einer Methode zu haben, die in einem bestimmten Spielstatus angibt, welche Züge in diesem Status verfügbar sind (und nachdem ich das berichtet habe, erinnerst du dich an nichts über den fraglichen Staat? Wenn das Objekt, das nach verfügbaren Zügen gefragt wird, keine Erinnerung an den Spielstatus behält, muss es keine Referenz beibehalten.
In einigen Fällen kann es von Vorteil sein, wenn das regelauswertende Objekt den Status beibehält. Wenn Sie der Meinung sind, dass eine solche Situation eintreten könnte, würde ich vorschlagen, eine Klasse "referee" hinzuzufügen und das Regelbuch eine Methode "createReferee" bereitstellen zu lassen. Im Gegensatz zum Regelwerk, das sich nicht darum kümmert, ob es sich um ein oder fünfzig Spiele handelt, würde ein Schiedsrichterobjekt erwarten, ein Spiel zu regieren. Es ist nicht zu erwarten, dass der gesamte Status in Bezug auf das Spiel, das es amtiert, zusammengefasst wird, es kann jedoch alle Informationen über das Spiel zwischengespeichert werden, die es als nützlich erachtet. Wenn ein Spiel die Funktion "Rückgängig" unterstützt, kann es hilfreich sein, wenn der Schiedsrichter ein Mittel zum Erstellen eines "Schnappschuss" -Objekts einschließt, das zusammen mit früheren Spielzuständen gespeichert werden kann. dieses Objekt sollte,
Wenn eine Kopplung zwischen den Aspekten der Regelverarbeitung und der Spielzustandsverarbeitung des Codes erforderlich sein könnte, wird die Verwendung eines Schiedsrichterobjekts es ermöglichen, eine solche Kopplung aus dem Hauptregelwerk und den Spielzustandsklassen herauszuhalten. Es kann auch möglich sein, dass neue Regeln Aspekte des Spielzustands berücksichtigen, die von der Spielzustandsklasse nicht als relevant erachtet werden (z. B. wenn eine Regel hinzugefügt wurde, die besagt, dass "Objekt X kein Y ausführen kann, wenn es sich jemals an Position Z befunden hat ", der Schiedsrichter könnte geändert werden, um zu verfolgen, welche Objekte sich an Ort Z befunden haben, ohne die Spielzustandsklasse ändern zu müssen).
quelle
Der richtige Umgang damit ist die Verwendung von Schnittstellen. Anstatt zwei Klassen voneinander zu kennen, muss jede Klasse eine Schnittstelle implementieren und auf die der anderen Klasse verweisen. Angenommen, Sie haben Klasse A und Klasse B, die sich gegenseitig referenzieren müssen. Lassen Sie die Klasse A die Schnittstelle A und die Klasse B die Schnittstelle B implementieren, dann können Sie die Schnittstelle B aus Klasse A und die Schnittstelle A aus Klasse B referenzieren. Klasse A kann sich dann in einem eigenen Projekt befinden, ebenso wie Klasse B. Die Schnittstellen befinden sich in einem separaten Projekt worauf sich beide anderen Projekte beziehen.
quelle