|
|
|||
Exkurs:Das Zuul-Projekt von BARNES und KÖLLING |
|||
| Wir unterbrechen den Lehrgang über "Abstrakte Datentypen" für eine kurze Weile und wenden uns wieder einem Projekt aus dem Buch "Objektorientierte Programmierung mit Java" von David J. BARNES und Michael KÖLLING zu. In dem Kapitel 7 dieses hervorragenden Buches (Engagierten Schülern zur Anschaffung empfohlen) geht es um ein kleines textbasiertes Spiel namens "Die Welt von Zuul". |
|||
Schritt 1: Das ProjektStarten Sie BlueJ und öffnen Sie das Projekt "Zuul-schlecht" aus dem Ordner "Kapitel07" der Original-Projektdateien. Wir wollen nun einmal ganz unvoreingenommen an das Projekt herangehen. Daher schauen wir nicht in das Buch von BARNES und KÖLLING hinein (zunächst jedenfalls nicht), sondern versuchen, das Projekt und die Beziehungen zwischen den Klassen ohne fremde Hilfe zu verstehen. Dabei hilft ein systematisches Vorgehen. |
|||
Schritt 2: Das KlassendiagrammWir betrachten uns nun das Klassendiagramm der Welt von Zuul.
1 Das Klassendiagramm des Projektes "Zuul-schlecht" Bevor wir nun wie wild auf die einzelnen Klassen klicken und uns den Quelltext ansehen, wollen wir das Diagramm als Ganzes betrachten. Es gibt fünf Klassen in dem Projekt, die untereinander in Beziehung stehen. Die Pfeile zeigen uns, welche Klasse von welcher anderen Klasse benötig wird. So scheint die Klasse Spiel im Mittelpunkt zu stehen, denn sie benötigt drei andere Klassen zum Funktionieren, nämlich Parser, Befehl und Raum. Die Klasse Parser benötigt ihrerseits die Klasse Befehlswoerter. Umgekehrt wird die Klasse Parser nicht nur von Spiel aufgerufen, sondern auch von Befehl. |
|||
Schritt 3: Die erste Klasse auswählenWelches ist die erste Klasse? Diese Frage stellt sich, wenn man sich in die Quelltexte der fünf Klassen vertiefen will. Welches die "erste" Klasse ist, hängt von dem Programmierkonzept ab, das man vertritt.Top-Down-ProgrammierungNach diesem Programmierkonzept entwickelt man zunächst den groben Rahmen des Programms, im Prinzip also die main()-Funktion. Dabei schreibt man aber keine Details in den Quelltext, sondern ruft Funktionen auf, die man erst später entwickeln wird. Man programmiert also von "oben nach unten". Nach diesem Programmierkonzept wäre die Klasse Spiel die erste Klasse, die man sich anschauen sollte. Spiel ruft nämlich fast alle anderen Klassen auf, die somit weiter "unten" in der Hierarchie stehen. Bottom-Up-ProgrammierungDies ist ein völlig anderes Programmierkonzept, was aber nicht heißt, dass es schlechter wäre als die Top-Down-Programmierung. Im Schulunterricht wird zwar die Top-Down-Programmierung zumindest theoretisch bevorzugt, weil man mit ihrer Hilfe sehr schön die "allgemeinen Prinzipien der Programmentwicklung demonstrieren" kann, in der Praxis des Schulunterrichts wird aber mindestens genauso häufig die Bottom-Up-Programmierung betrieben. Oder wie würden Sie es bezeichnen, wenn im Informatik-Unterricht erst die if-Anweisung, dann die while-Schleife, dann Arrays und schließlich Stacks, Queues und andere Datentypen behandelt werden? Bei der Bottom-Up-Programmierung baut man sich zunächst kleine, übersichtliche Bausteine. Hier kann man das Prinzip der Datenkapselung wunderbar anwenden, denn der Grundgedanke bei dieser Vorgehensweise ist der: "Wie kann ich diesen meinen neuen Baustein so absichern, dass ich ihn später in möglichst vielen verschiedenen Programmen verwenden kann, ohne dass ich irgendetwas am Quelltext des Bausteins verändern muss?" Genau das ist doch Datenkapselung! Wenn man dieses Programmierkonzept vetritt, wäre Spiel die letzte Klasse, die man betrachten würde. |
|||
Schritt 4: Die Klasse Spiel - InitialisierungBetrachten wir nach diesen langen Vorbemerkungen endlich die Klasse Spiel. Sie sehen, ich persönlich habe mich für das Top-Down-Vorgehen entschieden, obwohl ich eher die Bottom-Up-Programmierung betreibe, wenn ich selbst ein komplexes Programm entwerfe. Aber ich weiß ehrlich gesagt nicht, bei welcher der vier untergeordneten Klassen ich mit der Analyse anfangen soll, also fange ich mal mit der übergeordneten Klasse Spiel an.Die AttributeInteressanterweise verfügt die Klasse Spiel nur über zwei Attribute. Nach dem Klassendiagramm hätte man eigentlich mindestens drei Attribute erwartet, nämlich eines vom Typ Parser, eines vom Typ Raum, und eines vom Typ Befehl. Die beiden Attribute sind: private Parser parser; private Raum aktuellerRaum;Das Objekt parser hat offensichtlich etwas mit der Befehlserkennung zu tun (das ist halt die Aufgabe eines Parsers, wie Sie in der Stufe 12/2 noch erfahren werden), während das Objekt aktuellerRaum wohl das eigentliche Spielfeld darstellen soll. Der Konstruktor public Spiel()
{
raeumeAnlegen();
parser = new Parser();
}
Der Quelltext des Konstruktors ist recht einfach. Erst werden Räume angelegt, indem eine Funktion raeumeAnlegen() aufgerufen wird. Dann wird das Parser-Objekt parser erzeugt. |
Wichtiger Hinweis: Selbstverständlich können Sie es genauso machen wie ich: Gehen Sie mal völlig unvoreingenommen an das Projekt heran und verstehen Sie den Ablauf des Spiels sowie die Beziehungen zwischen den Klassen. Den Text dieser Folge schreibe ich eigentlich nur a) für mich selbst, damit ich das Spiel durchschaue und b) für die Schüler(innen), denen die vielen Klassen und Quelltexte des Spiels für ein solches Vorgehen zu unübersichtlich sind und die lieber eine konkrete Anleitung haben wollen. |
||
Schritt 5: Erzeugung der RäumeBetrachten wir nun den Quelltext der wichtigen Funktion raeumeAnlegen().Raum draussen,hoersaal,cafeteria,labor,buero; Innerhalb dieser Funktion werden fünf Räume als lokale Variablen deklariert, was mich persönlich zunächst ziemlich wundert. Wenn die Räume "nur" lokale Variablen sind, haben die anderen Funktionen der Klasse Spiel doch überhaupt keinen Zugriff auf diese Räume. Wie soll dann das Spiel funktionieren? Aber sehen wir erstmal weiter. draussen = new Raum("vor dem Haupteingang der Universität");
Mit diesem Befehl wird der Raum draussen erzeugt. Als Parameter wird dabei ein String mit einer Ortsbeschreibung übergeben. Die anderen vier Räume werden analog erzeugt. Im nächsten Schritt erhält jeder Raum maximal vier Ausgänge, offensichtlich sieht das Spiel vor, dass jeder Raum quadratisch ist und sich mitten in jeder Wand ein Ausgang befinden kann: draussen.setzeAusgaenge(null, hoersaal, labor, cafeteria); Der Raum draussen hat nur drei Ausgänge, in der ersten Wand befindet sich kein Ausgang.
2 Der Raumplan, wie er sich aus der Initialisierung der Ausgänge ergibt Wenn man sich anschaut, welcher Raum mit welchem anderen Raum eine Verbindung hat, so kommt man unweigerlich zu dem Raumplan, wie er in Abbildung 2 zu sehen ist. |
|
||
Schritt 6: Das SpielDie Analyse des Quelltextes der Funktion spielen() zeigt, dass hier sehr schön das Top-Down-Prinzip verwirklicht wurde.Zunächst wird ein Willkommenstext ausgegeben, die Aufgabe wird an eine entsprechende Funktion delegiert. Dann kommt die Hauptschleife des Spiels, die immer wieder durchlaufen wird, bis das Spiel abgebrochen wird. Die Hauptschleife besteht nur aus zwei Zeilen, nämlich erstens Analyse des nächsten Befehls und zweitens Ausführung des nächsten Befehls. Nach Beendigung der Hauptschleife wird ein Abschiedsgruß ausgegeben. Und das war's auch schon. |
|
||
Schritt 7: Das Starten des SpielsBeenden wir diese erste Seite der Folge 17 mit einer näheren Analyse der Funktion willkommenstextAusgeben(), die eben wesentlich mehr macht als nur einen Willkommenstext auszugeben und die daher eigentlich einen anderen Namen haben müsste, z.B. "initialisierung()".Zuerst wird tatsächlich ein Willkommenstext ausgegeben. Anschließend erhält der Spieler den Hinweis, in welchem Raum er sich bei Beginn des Spiels aufhält. Schließlich wird dem Spieler mitgeteilt, in welcher Richtung des Raumes sich Ausgänge befinden. |
|
||
Schritt 8: Die BefehlsverarbeitungNachdem das Spiel gestartet ist, erfolgt die Hauptschleife des Spiels, die aus den beiden Abschnitten Befehlserkennung und Befehlsverarbeitung besteht. Für die Verarbeitung der Befehle ist die Funktion verarbeiteBefehl(Befehl befehl) zuständig. Diese Funktion kennt nur drei Fälle: Der Hilfebefehl "help" - hier wird ein Hilfstext ausgegeben Der Raumwechselbefehl "go" - hier wird ein anderer Raum aufgesucht Der Beendigungsbefehl "quit" - hier wird das Spiel beendet. |
|||
Schritt 9: Der RaumwechselDieser Befehl ist sicherlich der wichtigste des Spiels. Eine eigene Funktion wechsleRaum(Befehl befehl) ist hierfür zuständig. Offensichtlich kann ein Befehl aus zwei Worten bestehen, denn es gibt hier eine Zeile String richtung = befehl.gibZweitesWort(); In der nun folgenden Sequenz von if-Anweisungen wird der nächste Raum bestimmt, z.B. mit
if(richtung.equals("north"))
naechsterRaum = aktuellerRaum.nordausgang;
Falls der nächste Raum tatsächlich existiert, so wird das Attribut aktuellerRaum auf den neuen Raum gesetzt, und anschließend wird dem Spieler eine entsprechende Meldung angezeigt, in welchem Raum er sich jetzt befindet und in welcher Richtung dieser neue Raum Ausgänge hat. |
|||
Aufgabe 17.1 (3 Punkte)Die Angaben in Bild 1 sind nicht ganz korrekt. Das merken Sie schnell, wenn Sie das Spiel einmal spielen. Zeichnen Sie einen neuen, besseren Lageplan der Räume. |
|||
und weiter mit Teil 2
|
|||
|
Diese HTML-Seite wurde erstellt von Ulrich Helmich am 25. September 2005. |
|||