Der Schlüssel liegt hier nicht nur in der Trennung von Anliegen , sondern auch im Prinzip der Einzelverantwortung . Die beiden sind im Grunde verschiedene Seiten derselben Medaille: Wenn ich SOC denke, denke ich von oben nach unten (ich habe diese Bedenken, wie trenne ich sie?), Während SRP mehr von unten nach oben ist (ich habe dieses Objekt, hat es eine einzelnes Anliegen? Sollte es geteilt werden? Sind seine Anliegen bereits zu stark gespalten?).
In Ihrem Beispiel haben Sie die folgenden Entitäten und deren Verantwortlichkeiten:
- Spiel: Dies ist der Code, der das Programm zum "Los" bringt.
- GameBoard: Behält den Status des Spielbereichs bei.
- Karte: eine einzelne Einheit auf dem Spielbrett.
- Spieler: Führt Aktionen aus, die den Status des Spielbretts ändern.
Sobald Sie über die Einzelverantwortung jedes Unternehmens nachdenken, werden die Linien klarer.
In einer App wie einem Spiel gibt es eine Hauptklasse, in der die Hauptschleife ausgeführt wird, z. B. Programm oder Spiel. Meine Frage ist, behalte ich jeden Verweis auf jede Instanz einer Klasse in dieser Klasse bei und mache dies zur einzigen Art und Weise, wie sie interagieren?
Hier sind wirklich zwei Punkte zu beachten. Die erste Entscheidung ist, was Entitäten über andere Entitäten wissen. Welche Entitäten gehören zu anderen Entitäten?
Schauen Sie sich die oben beschriebenen Verantwortlichkeiten an. Die Spieler führen Aktionen aus , die den Status des Spielbretts ändern. Mit anderen Worten, Spieler senden Nachrichten an (Aufrufmethoden auf) dem Spielbrett. Bei diesen Nachrichten handelt es sich wahrscheinlich um Karten: Beispielsweise kann ein Spieler eine Karte in seine Hand auf das Brett legen oder den Status einer vorhandenen Karte ändern (z. B. eine Karte umdrehen oder an einen neuen Ort verschieben).
Es ist klar, dass ein Spieler über das Spielbrett Bescheid wissen muss, was der Annahme widerspricht, die Sie in Ihrer Frage gemacht haben. Andernfalls muss der Spieler eine Nachricht an das Spiel senden, die diese Nachricht dann an das Spielbrett weiterleitet. Da die Spieler Aktionen auf dem Spielbrett ausführen , müssen sie über das Spielbrett Bescheid wissen. Dies erhöht die Kopplung: Anstatt dass der Spieler die Nachricht direkt sendet, müssen jetzt zwei Akteure wissen, wie sie diese Nachricht senden. Das Demeter-Gesetz impliziert, dass in diesem Szenario, wenn ein Objekt auf ein anderes Objekt einwirken muss, dieses andere Objekt über einen Parameter übergeben werden sollte, um die Kopplung zu verringern.
Wo speichern Sie als nächstes welchen Zustand? Das Spiel ist hier der Treiber, es muss alle Objekte entweder direkt oder über einen Proxy knittern (z. B. eine Factory oder einen Konstruktor, den das Spiel aufruft). Die logische nächste Frage ist, welche Objekte welche anderen Objekte benötigen. Dies ist im Grunde das, was ich oben gefragt habe, aber eine andere Art, es zu fragen.
So würde ich es gestalten:
Das Spiel erstellt alle für das Spiel erforderlichen Objekte.
Das Spiel mischt die Karten und teilt sie nach dem jeweiligen Spiel auf (Poker, Solitaire usw.).
Das Spiel legt die Karten an ihren ursprünglichen Positionen ab: vielleicht einige auf dem Spielbrett, andere in den Händen der Spieler.
Das Spiel geht dann in seine Hauptschleife, die eine Runde darstellt.
Jede Runde würde so aussehen:
Das Spiel sendet eine Nachricht an den aktuellen Spieler (ruft eine Methode auf) und gibt einen Verweis auf das Spielbrett.
Der Player führt die interne Logik (Computer-Player) oder Benutzerinteraktion aus, die erforderlich ist, um zu bestimmen, welches Spiel ausgeführt werden soll.
Der Spieler sendet eine Nachricht an das Spielbrett, in der er aufgefordert wird, den Status des Spielbretts zu ändern.
Das Spielbrett entscheidet , ob der Zug gültig ist oder nicht (es ist verantwortlich für die Aufrechterhaltung der gültigen Spielzustand).
Die Kontrolle kehrt zum Spiel zurück und entscheidet dann, was als nächstes zu tun ist. Auf Gewinnbedingungen prüfen? Zeichnen? Nächster Spieler? Nächste Runde? Hängt vom jeweiligen Kartenspiel ab.
Sollte es an der Spielklasse liegen, die Karten auf das Brett zu legen, oder ist es sinnvoller, dass sie, da es sich um die Aktion des Spielers handelt, innerhalb der Spielerklasse liegen sollte.
Beide: Das Spiel ist für die Ersteinrichtung verantwortlich, aber der Spieler führt Aktionen auf dem Brett aus. GameBoard ist für die Gewährleistung eines gültigen Status verantwortlich. Beispielsweise kann im klassischen Solitaire nur die oberste Karte auf einem Stapel offen liegen.
Zurück zu meinem ursprünglichen Punkt: Sie haben die richtigen Trennungen von Bedenken. Sie haben die richtigen Objekte identifiziert. Was Sie auslöste, war herauszufinden, wie Nachrichten durch das System fließen und welche Objekte Verweise auf andere Objekte enthalten sollten. Ich würde es so gestalten, was Pseudocode ist:
class Game {
main();
}
class GameBoard {
// Data structures specific to the game being played. There is a
// lot of hand-waving here to give the general idea without
// getting bogged down in the implementation.
Map<Card, Location> cards;
GameBoard(Map<Card, Location>);
// Return false if the move is invalid.
bool flip(Card);
bool move(Card, Location);
}
class Card {
// Make Rank and Suit enums.
Suit suit;
Rank rank;
bool faceUp;
}
class Player {
Set<Card> hand;
Player(Set<Card>);
void takeTurn(GameBoard);
}