Читать книгу Dojos für Entwickler - Stefan Lieser - Страница 6

Vom Problem zur Lösung

Оглавление

Nun wollen Sie sicher so langsam auch mal Code sehen. Doch vorher muss noch geklärt werden, was aus den einzelnen Funktionseinheiten werden soll. Werden sie jeweils eine Klasse? Eher nicht, denn dann wären Spiellogik und Benutzerschnittstelle nicht ausreichend getrennt. Somit werden Benutzerschnittstelle und Spiellogik mindestens eigenständige Komponenten. Die Funktionseinheiten innerhalb der Spiellogik hängen sehr eng zusammen. Alle leisten einen Beitrag zur Logik. Ferner scheint mir die Spiellogik auch nicht komplex genug, um sie weiter aufzuteilen. Es bleibt also bei den beiden Komponenten Benutzerschnittstelle und Spiellogik.

Um beide zu einem lauffähigen Programm zusammenzusetzen, brauchen wir noch ein weiteres Projekt. Seine Aufgabe ist es, eine EXE-Datei zu erstellen, in der die beiden Komponenten zusammengeführt werden. So entstehen am Ende drei Komponenten.

Abbildung 3 zeigt die Solution für die Spiellogik. Sie enthält zwei Projekte: eines für die Tests, ein weiteres für die Implementierung.


[Abb. 3] Aufbau der Solution.

Die Funktionseinheit Spielzustand zerfällt in drei Teile. Beginnen wir mit dem Legen von Steinen. Beim Legen eines Steins in das Spielfeld wird die Spalte angegeben, in die der Stein gelegt werden soll. Dabei sind drei Fälle zu unterscheiden: Die Spalte ist leer, enthält schon Steine oder ist bereits voll.

Es ist naheliegend, das Spielfeld als zweidimensionales Array zu modellieren. Jede Zelle des Arrays gibt an, ob dort ein gelber, ein roter oder gar kein Stein liegt. Der erste Index des Arrays bezeichnet dabei die Spalte, der zweite die Zeile. Beim Platzieren eines Steins muss also der höchste Zeilenindex innerhalb der Spalte ermittelt werden. Ist dabei das Maximum noch nicht erreicht, kann der Stein platziert werden.

Bleibt noch eine Frage: Wie ist damit umzugehen, wenn ein Spieler versucht, einen Stein in eine bereits gefüllte Spalte zu legen? Eine Möglichkeit wäre: Sie stellen eine Methode bereit, die vor dem Platzieren eines Steins aufgerufen werden kann, um zu ermitteln, ob dies in der betreffenden Spalte möglich ist. Der Code sähe dann ungefähr so aus:

if(spiel.KannPlatzieren(3)) {

spiel.LegeSteinInSpalte(3);

}

Dabei gibt der Parameter den Index der Spalte an, in die der Stein platziert werden soll. Das Problem mit diesem Code ist, dass er gegen das Prinzip „Tell don’t ask" verstößt. Als Verwender der Funktionseinheit, die das Spielbrett realisiert, bin ich gezwungen, das API korrekt zu bedienen. Bevor ein Spielstein mit LegeSteinlnSpalte() in das Spielbrett gelegt wird, müsste mit KannPlatzieren() geprüft werden, ob dies überhaupt möglich ist. Nach dem „Tell don’t ask"-Prinzip sollte man Klassen so erstellen, dass man den Objekten der Klasse mitteilt, was zu tun ist - statt vorher nachfragen zu müssen, ob man eine bestimmte Methode aufrufen darf. Im Übrigen bleibt bei der Methode LegeSteinInSpalte() das Problem bestehen: Was soll passieren, wenn die Spalte bereits voll ist?

Eine andere Variante könnte sein, die Methode LegeSteinlnSpalte() mit einem Rückgabewert auszustatten. War das Platzieren erfolgreich, wird true geliefert, ist die Spalte bereits voll, wird false geliefert. In dem Fall müsste sich der Verwender der Methode mit dem Rückgabewert befassen. Am Ende soll der Versuch, einen Stein in eine bereits gefüllte Spalte zu platzieren, dem Benutzer gemeldet werden. Also müsste der Rückgabewert bis in die Benutzerschnittstelle transportiert werden, um dort beispielsweise eine Messagebox anzuzeigen.

Die Idee, die Methode mit einem Rückgabewert auszustatten, verstößt jedoch ebenfalls gegen ein Prinzip, nämlich die „Command/Query Separation". Dieses Prinzip besagt, dass eine Methode entweder ein Command oder eine Query sein sollte, aber nicht beides. Dabei ist ein Command eine Methode, die den Zustand des Objekts verändert. Für die Methode LegeSteinlnSpalteO trifft dies zu: Der Zustand des Spielbretts ändert sich dadurch. Eine Query ist dagegen eine Methode, die eine Abfrage über den Zustand des Objekts enthält und dabei den Zustand nicht verändert. Würde die Methode LegeSteinInSpalte() einen Rückgabewert haben, wäre sie dadurch gleichzeitig eine Query.

Nach diesen Überlegungen bleibt nur eine Variante übrig: Die Methode LegeSteinInSpalte() sollte eine Ausnahme auslösen, wenn das Platzieren nicht möglich ist. Die Ausnahme kann in der Benutzerschnittstelle abgefangen und dort in einer entsprechenden Meldung angezeigt werden. Damit entfällt die Notwendigkeit, einen Rückgabewert aus der Spiellogik bis in die Benutzerschnittstelle zu transportieren. Ferner sind die Prinzipien „Tell don’t ask" und „Com-mand/Query Separation" eingehalten.

Dojos für Entwickler

Подняться наверх