Objektorientierte Programmierung
LVA 185.162, VL 2.0, 2009 W
4. Laborübungsaufgabe
Themen:
Untertypbeziehungen, Vererbung, Zusicherungen
Termine:
| Ausgabe: |
04.11.2009 |
| reguläre Abgabe: |
11.11.2009, 13:45 Uhr |
| nachträgliche Abgabe: |
18.11.2009, 13:45 Uhr |
Abgabeverzeichnis:
Gruppe/Aufgabe4
Programmaufruf:
java Test
Grundlage:
die ersten zwei Kapitel im
Skriptum, Schwerpunkt auf Kapitel 2
Aufgabe
Welche Aufgabe zu lösen ist:
Folgendes Interface ist vorgegeben:
public interface Block {
// instances represent rectangular blocks of
// printable characters containing some text
int width();
// returns the width of the block as number
// of characters per line
int height();
// returns the height of the block as number
// of lines
void setText (String text);
// changes the text contained in the block
String toString();
// returns this block as String
}
Es sollen folgende Klassen erstellt werden, die Untertypen von
Block sind:
- In TextBlock werden Höhe und Breite des Blocks (jeweils größer Null) über Parameter des Konstruktors festgelegt und sind nicht änderbar.
Die Methode setText stellt sicher, dass im übergebenen String text nur druckbare Zeichen (einschließlich Leerzeichen, aber ohne Tabulatorzeichen) und Zeilenumbrüche Berücksichtigung finden;
alle anderen Zeichen werden ohne Rückmeldung aus dem intern gespeicherten Text entfernt.
Die Methode toString gibt den durch setText zuletzt festgelegten Text in einem Block vorgegebener Breite und Höhe zurück.
Der Text wird links oben beginnend in den Block geschrieben, wobei nur bei jedem Zeilenumbruch in die nächste Zeile gesprungen wird, sowie zu lange Zeilen abgeschnitten und zu kurze Zeilen mit Leerzeichen am Ende aufgefüllt werden.
Enthält der Text zu viele Zeilen, wird das über die Höhe des Blocks hinausgehende Ende abgeschnitten.
Enthält der Text weniger Zeilen, werden entsprechend viele nur aus Leerzeichen bestehenden Zeilen angehängt.
Bevor setText zum ersten mal aufgerufen wird, besteht der Block nur aus Leerzeichen.
- FillBlock entspricht TextBlock bis auf einen einzigen Unterschied:
Zu kurze und fehlende Zeilen werden (statt durch Leerzeichen) durch lauter gleiche druckbare Zeichen aufgefüllt, die beim Aufruf des Konstruktors festgelegt werden.
Ein gerade erst erzeugter Block enthält nur solche Zeichen.
Auch Leerzeichen können im Konstruktor als Füllzeichen festgelegt werden.
- WindowBlock entspricht TextBlock bis auf die Änderbarkeit der Blockgröße:
Breite und Höhe sind durch je eine zusätzliche Methode änderbar.
Die Methode toString gibt einen Block von gerade aktueller Breite und Höhe zurück.
- ScrollBlock entspricht WindowBlock bis auf den dargestellten Textausschnitt.
Der durch toString zurückgebene String enthält nicht unbedingt nur den linken oberen Teil eines zu langen Textes, sondern einen wählbaren Ausschnitt daraus.
Über zwei zusätzliche Methoden wird bestimmt, um wie viele Zeichen bzw. Zeilen (größer oder gleich Null) der dargestellte Text - wie beim Scrollen in einem Editor - nach links bzw. oben verschoben wird.
Der Block enthält also entsprechend viele Zeilen zu Beginn des Textes bzw. entsprechend viele Zeichen zu Beginn jeder Zeile nicht.
In einer neuen Instanz von ScrollBlock sind die entsprechenden Werte auf Null gesetzt.
Solange diese Werte nicht geändert werden, verhällt sich eine Instanz von ScrollBlock wie eine von WindowBlock.
Versehen Sie Klassen und Interfaces mit allen notwendigen Zusicherungen und stellen Sie sicher, dass Sie nur dort eine Vererbungsbeziehung (extends oder implements) verwenden, wo tatsächlich eine Untertypbeziehung (auch hinsichtlich der Zusicherungen) besteht.
Zwischen je zwei Untertypen von Block soll eine Untertypbeziehung bestehen, wenn dies aufgrund der Schnittstellen und Zusicherungen möglich ist.
Schreiben Sie so wenig Programmcode wie möglich.
Verwenden Sie abstrakte Klassen und Interfaces zwischen Block und den oben beschriebenen Klassen, wenn dies für die Wiederverwendung von Code vorteilhaft ist.
Schreiben Sie eine Klasse Test zum Testen Ihrer Lösung.
Überprüfen Sie so gut Sie können mittels Testfällen, ob dort, wo Sie eine Untertypbeziehung annehmen, Ersetzbarkeit gegeben ist.
Wenn zwischen zwei Klassen keine Untertypbeziehung besteht, geben Sie in einem Kommentar in der Testklasse ein kurzes Beispiel an, in dem die Ersetztbarkeit offensichtlich verletzt ist.
Wie die Aufgabe zu lösen ist:
Anzahl und Umfang der zur Lösung benötigten Klassen sind klein.
Die Schwierigkeit liegt darin, alle Untertypbeziehungen zu finden und die Ersetzbarkeit sicherzustellen.
Dieser Punkt ist wesentlich für die Beurteilung.
Vererbungsbeziehungen, die nicht gleichzeitig auch Untertypbeziehungen sind, führen zu sehr hohen Punkteabzügen.
Ebenso gibt es hohe Punkteabzüge für nicht wahrgenommene Gelegenheiten, Untertypbeziehungen zwischen den Untertypen von
Block herzustellen.
Eine Grundlage für das Auffinden der Untertypbeziehungen sind gute Zusicherungen in Form von Kommentaren.
Alle wesentlichen Zusicherungen kommen bereits in obigen Beschreibungen der benötigten Klassen vor.
Sie brauchen die einzelnen Beschreibungsteile nur mehr den richtigen Zusicherungsarten in den entsprechenden Teilen der Klassen zuzuordnen.
Untertypbeziehungen ergeben sich dann aus den erlaubten Beziehungen zwischen Zusicherungen in Unter- und Obertypen.
Es hat sich als günstig erwiesen, alle Zusicherungen, die in einem Obertyp gelten, im Untertyp nochmals hinzuschreiben, da sie sonst leicht übersehen werden.
Vergewissern Sie sich der Korrektheit der Untertypbeziehungen zusätzlich über geeignete Testfälle.
Die Anzahl der Testfälle ist nicht entscheidend, wohl aber deren Qualität:
Es kommt darauf an, dass die Testfälle mögliche Verletzungen der Ersetzbarkeit aufdecken können.
Beispielsweise muss überprüft werden, ob toString auch nach Ersetzungen und anderen Methodenaufrufen das zurückgibt, was erwartet wird.
Umgekehrt sollen Sie sich auch vergewissern, dass Sie keine Gelegenheit für Untertypbeziehungen verpasst haben, indem Sie Beispiele dafür finden, wie angenommene Untertypbeziehungen das Ersetzbarkeitsprinzip verletzen würden.
Schreiben Sie die Gegenbeispiele als Kommentare neben den Testfällen für Ersetzbarkeit in die Testklasse.
Zusicherungen in Testklassen werden aus praktischen Überlegungen bei der Beurteilung nicht berücksichtigt.
Sorgen Sie aber bitte dafür, dass ein Aufruf von java Test einigermaßen nachvollziehbaren Output generiert.
Zur Lösung dieser Aufgabe müssen Sie Untertypbeziehungen und vor allem den Einfluss von Zusicherungen auf Untertypbeziehungen im Detail verstehen.
Holen Sie sich entsprechende Informationen aus Kapitel 2 des Skriptums.
Folgende zusätzlichen Informationen könnten hilfreich sein:
- Konstruktoren werden in einer bestimmten Klasse aufgerufen und sind vom Ersetzbarkeitsprinzip nicht betroffen.
Konstruktoren haben wie statische Methoden keinen direkten Einfluss auf Untertypbeziehungen.
- Zur Lösung der Aufgabe benötigen Sie keine Exceptions.
Sollten Sie dennoch Exceptions verwenden, bedenken Sie, dass eine Instanz eines Untertyps nur dann eine Exception werfen darf, wenn man auch von einer Instanz eines Obertyps in derselben Situation diese Exception erwarten würde.
- In der Aufgabe benötigen Sie auch keine Generizität.
Sollten Sie dennoch Generizität verwenden, bedenken Sie, dass sich viele Warnungen des Compilers im Zusammenhang mit Generizität auf Verletzungen der Ersetzbarkeit beziehen und daher schwerwiegende Fehler im Sinne dieser Aufgabe darstellen.
Vermeiden Sie daher diesbezügliche Meldungen des Compilers.
Die Vermeidung unnötigen Codes und die direkte Codewiederverwendung fließen zwar auch in die Beurteilung ein, aber in bei weitem geringerem Ausmaß als die Ersetzbarkeit.
Auf zusätzlich eingeführten abstrakten Klassen und Interfaces müssen natürlich auch Untertypbeziehungen gelten, die auf dieselbe Weise zu überprüfen sind wie für die vorgegebenen Klassen.
Es ist daher ratsam, solche Zusätze nicht oder nur sparsam einzusetzen, um unnötige mögliche Fehlerquellen zu vermeiden.
Lassen Sie sich von der Form der Beschreibung der benötigten Klassen nicht täuschen.
Daraus, dass die Beschreibung einer Klasse die Beschreibung einer anderen Klasse wiederverwendet, folgt noch lange keine Ersetzbarkeit.
Generell sind Sie wahrscheinlich auf dem falschen Weg, wenn es den Anschein hat, A könne Untertyp von B und B gleichzeitig Untertyp von A sein, obwohl A und B ungleich sind.
Achten Sie auf richtige Sichtbarkeit.
Alle oben beschriebenen Klassen und Methoden sollen überall verwendbar sein, die Sichtbarkeit von Implementierungsdetails soll aber möglichst stark eingeschränkt werden.
Nutzen Sie die vorgesehene Zeit bis 11. November zur Lösung der aktuellen Aufgabe, und stellen Sie noch ausstehende Arbeiten an früheren Aufgaben (vorübergehend) ein.
Am 11. November gibt es keine neue Aufgabe, und ab dann haben Sie etwas Zeit, um noch ausstehende Arbeiten entsprechend den Anweisungen Ihrer Tutorin oder Ihres Tutors nachzuholen.
Was man generell beachten sollte:
Ab dieser Aufgabe werden keinerlei Ausnahmen bezüglich des Abgabetermins gemacht.
Was nicht rechtzeitig im richtigen Verzeichnis am Übungsrechner steht, wird bei der Beurteilung nicht berücksichtigt.
Da es ab jetzt um 100 Punkte pro Aufgabe geht, wäre jede Ausnahme den anderen Gruppen gegenüber ungerecht.
Bitte verwenden Sie in dieser und den folgenden Aufgaben keine Unterverzeichnisse im Abgabeverzeichnis (und damit auch keine Pakete).
Das hat mehrere Gründe:
- Da die Verzeichnisstruktur auf dem Rechner zu Hause manchmal eine andere ist als am Übungsrechner, wäre es sonst bei der Abgabe gelegentlich notwendig, package-Anweisungen zu ändern.
Darauf vergisst man leicht oder führt Änderungen in letzter Minute nur unvollständig durch.
Derartige Fehler sind immer wieder Ursache für Syntaxfehler und unnötige Punkteverluste.
- Unterverzeichnisse enthalten oft Dateien (z.B. Backups), die unabsichtlich in das Abgabeverzeichnis kopiert werden und zu Syntaxfehlern führen können.
- Gut in Unterverzeichnissen versteckte Dateien können bei der Beurteilung leicht übersehen werden.
- Für kleine Aufgaben wie diese bringen Pakete keine Vorteile.
Schreiben Sie nicht mehr als eine Klasse in jede Datei (ausgenommen geschachtelte Klassen), halten Sie sich an übliche Namenskonventionen in Java (Großschreibung für Namen von Klassen und Interfaces, kleine Anfangsbuchstaben für Variablen und Methoden, etc.), und verwenden Sie die Namen, die in der Aufgabenstellung vorgegeben sind.
Damit erhöhen Sie die Lesbarkeit Ihrer Programme ganz wesentlich.
Außerdem können derartige Fehler
zu Punkteverlusten führen.
Lassen Sie vorgegebene Dateien im Abgabeverzeichnis (wie Block.java in Gruppe/Aufgabe4) bitte unverändert.
Falls Sie Ihre Lösung auf dem eigenen Rechner erstellen, kopieren Sie bitte vorher die vorgegebenen Dateien auf Ihren Rechner, um unterschiedliche Ausgangssituationen zu vermeiden.
Warum die Aufgabe diese Form hat:
Im Gegensatz zu vielen anderen Aufgaben sind die in dieser Aufgabe zu implementierenden Klassen ziemlich klar spezifiziert.
Der Grund liegt darin, dass die Beschreibungen der Klassen keinerlei Interpretationsspielraum bezüglich der Ersetzbarkeit bieten sollen.
Die Aufgabe ist so formuliert, dass die Untertypbeziehungen (abgesehen von abstrakten Klassen und Interfaces, die Sie vielleicht zusätzlich einführen) eindeutig sind.
Über Testfälle und Gegenbeispiele in Kommentaren sollen Sie selbst in der Lage sein, große Fehler in der Struktur der Lösung selbst zu finden.
Eine Voraussetzung für das Erkennen der richtigen Lösung und deren Eindeutigkeit ist aber ein gutes Verständnis der Ersetzbarkeit.
Wenn Ihnen mehrere Lösungsmöglichkeiten (hinsichtlich der Klassenstruktur ohne eigene abstrakte Klassen und Interfaces) als gleichermaßen richtig erscheinen, sollten Sie sich nocheinmal Kapitel 2 des Skriptums anschauen.