Objektorientierte Programmierung
LVA 185.162, VL 2.0, 2008 W
5. Übungsaufgabe
Themen:
gebundene Generizität, Sichtbarkeit, Subtyping, Container und Iteratoren
Termine:
| Ausgabe: |
13.11.2008 |
| reguläre Abgabe: |
20.11.2008, 12:45 Uhr |
| nachträgliche Abgabe: |
27.11.2008, 12:45 Uhr |
Abgabeverzeichnis:
Gruppe/Aufgabe5
Programmaufruf:
java Test
Grundlage:
Skriptum bis Seite 113
Aufgabe
Welche Aufgabe zu lösen ist:
Entwickeln Sie Java-Klassen entsprechend folgender Beschreibungen:
- Eine Instanz von SortedSet stellt eine als einfache Liste implementierte Menge dar, deren Elemente aufsteigend sortiert sind.
Ein Typparameter bestimmt den Typ der Elemente.
Die Elemente müssen eine Methode faster mit einem Parameter unterstützen, die genau dann true zurückgibt, wenn this (auf nicht näher bestimmte Weise) schneller als das übergebene Argument ist.
SortedSet implementiert folgende Methoden:
- insert nimmt ein Argument, das an geeigneter Stelle eingefügt wird, wenn nicht bereits ein identisches Element vorhanden war.
Mehrere gleiche aber nicht identische Elemente dürfen in der Menge sein.
- iterator liefert als Ergebnis einen Iterator (mit den Methoden next und hasNext), über den nacheinander auf alle Elemente der Menge in aufsteigender Reihenfolge zugegriffen werden kann.
- AnnotatedSet unterscheidet sich von SortedSet dadurch, dass jedes Element der Menge mit Anmerkungen versehen sein kann.
Der Typ der Anmerkungen wird durch einen weiteren Typparameter bestimmt.
In AnnotatedSet werden folgende Methoden benötigt:
- insert hat dieselbe Funktionalität wie in SortedSet.
- annotate hat zwei Argumente.
Eines bestimmt das Element der Menge, das mit einer Anmerkung versehen werden soll, das andere die entsprechende Anmerkung.
Falls das Objekt in der Menge vorkommt, wird die Anmerkung zur Liste der Anmerkungen des Elements hinzugefügt, sonst bleibt der Zustand der Menge unverändert.
- iterator liefert als Ergebnis wie in SortedSet einen Iterator, über den auf alle Elemente in aufsteigender Reihenfolge zugegriffen werden kann.
Neben den Methoden next und hasNext unterstützt dieser Iterator die Methode iterator, die einen weiteren Iterator über alle Anmerkungen zum zuletzt über next ausgelesenen Element (bzw. einen leeren Iterator, wenn davor noch nichts ausgelesen wurde) liefert.
Dieser Iterator gibt die Anmerkungen in beliebiger Reihenfolge zurück.
Wenn möglich soll AnnotatedSet ein Untertyp von SortedSet sein.
- Racer ist eine abstrakte Klasse oder ein Interface, das die Methoden faster (wie oben) und speed unterstützt, wobei speed eine maximale Geschwindigkeit in km/h zurückgibt und faster genau dann true liefert, wenn speed in this größer ist als im Argument.
- Horse und Leopard sind Unterklassen von Racer.
Zusätzlich zu den in Racer beschriebenen Methoden hat Horse eine Methode zur Abfrage der Zeit, die ein Pferd die Renngeschwindigkeit halten kann, und Leopard hat eine zur Abfrage der Strecke, die ein Leopard in hoher Geschwindigkeit zurücklegen kann.
- Fuse ist eine einfache Klasse, deren Instanzen Schmelzsicherungen sind.
Die Methode time gibt die Zeit vom Auftreten eines Kurzschlusses bis zum Durchbrennen der Sicherung in Millisekunden zurück, und faster liefert true, wenn time in this kleiner als im Argument ist.
Fuse ist kein Untertyp von Racer und Racer kein Untertyp von Fuse.
Von allen oben beschriebenen Klassen und Methoden wird erwartet, dass sie überall verwendbar sind.
Der Bereich, in dem weitere eventuell benötigte Methoden, Klassen und Interfaces sichtbar sind, soll jedoch so klein wie möglich gehalten werden.
Alle Klassen in dieser Aufgabe sind ohne Verwendung von Arrays, ohne vorgefertigte Container-Klassen (wie LinkedList, HashSet, etc.) und ohne vorgefertigte Iterator-Implementierungen zu lösen.
Die benötigten Container und Iteratoren sind selbst zu schreiben.
Typsicherheit soll so weit wie möglich vom Compiler garantiert werden.
Auf die Verwendung von Typumwandlungen (casts) und ähnliche Techniken ist daher zu verzichten, und der Compiler darf keine Hinweise auf mögliche Probleme im Zusammenhang mit Generizität geben.
Entsprechende Überprüfungen durch den Compiler dürfen nicht ausgeschaltet werden.
Ein Aufruf von java Test soll wie gewohnt die wichtigsten Normal und Grenzfälle überprüfen und die Ergebnisse in allgemein verständlicher Form darstellen.
Anders als in bisherigen Aufgaben sind die Überprüfungen jedoch vorgegeben und in dieser Reihenfolge auszuführen:
- Erzeugen Sie eine Instanz von SortedSet deren Elemente vom Typ Fuse sind.
Fügen Sie einige Schmelzsicherungen in unsortierter Reihenfolge ein, lesen Sie alle Elemente der Menge über den Iterator aus, und schreiben Sie die Durchbrenn-Zeiten der Sicherungen in die Standard-Ausgabe.
- Erzeugen Sie eine Instanz von AnnotatedSet deren Elemente vom Typ Horse und Anmerkungen vom Typ Leopard sind - nicht sehr sinnvoll, aber zum Testen gut geeignet.
Fügen Sie einige Pferde (in unsortierter Reihenfolge) und Leoparden ein, lesen Sie alles über die Iteratoren aus, und schreiben Sie die Werte für das Durchhaltevermögen der Pferde und Leoparden (in den jeweiligen Messeinheiten) in die Standard-Ausgabe.
- Falls AnnotatedSet mit entsprechenden Typparameterersetzungen ein Untertyp von SortedSet ist, betrachten Sie die in Punkt 2 erzeugte Menge als Instanz von SortedSet, fügen Sie noch ein (oder einige) Pferd(e) ein, lesen Sie alle Elemente über den Iterator aus, und schreiben Sie das Durchhaltevermögen der Pferde in die Standard-Ausgabe.
Dieser Punkt entfällt, wenn AnnotatedSet kein Untertyp von SortedSet ist.
- Erzeugen Sie eine Instanz von SortedSet deren Elemente vom Typ Racer sind.
Lesen Sie alle Elemente und Anmerkungen der in Punkt 2 erzeuten (und möglicherweise in Punkt 3 erweiterten) Menge aus, und fügen Sie diese (sowohl Pferde als auch Leoparden) in die neue Menge ein.
Lesen Sie alle Elemente der neuen Menge aus, und schreiben Sie die Höchstgeschwindigkeiten in die Standard-Ausgabe.
Warum die Aufgabe diese Form hat:
Die Aufgabe ist so konstruiert, dass dabei einige Schwierigkeiten auftauchen, für die wir bereits Lösungsmösungsmöglichkeiten kennengelernt haben.
Beispielsweise wird gebundene Generizität benötigt, und vermutlich ist zur Festlegung der Typschranken ein zusätzlicher Typ notwendig, der in der Aufgabenstellung selbst nicht vorkommt.
Eine weitere Schwierigkeit kommt durch die binäre Methode
faster hinein.
Mittels Untertypbeziehungen lässt sich der Parametertyp der Methode nicht vernünftig ausdrücken, wohl aber durch Generizität, dabei aber nur über eine Typebene hinweg.
Durch die Typhierarchie auf
Racer,
Horse und
Leopard muss Generizität über mehrere Ebenen hinweg betrachtet werden (da vereinfachende Sichtweisen durch den von dieser Hierarchie unabhängigen Typ
Fuse ausgeschlossen sind).
Dieser Teil der Aufgabe ist wahrscheinlich nur durch Verwendung von Wildcards lösbar.
Die vorgegebenen Testfälle stellen sicher, dass die Schwierigkeiten erkannt werden, und deren Umgehung wird dadurch unmöglich gemacht, dass keine Typumwandlungen verwendet werden dürfen.
Neben Techniken zur Lösung der speziellen Schwierigkeiten wird in dieser Aufgabe auch der Umgang mit Sichtbarkeit und Untertypbeziehungen auf generischen Typen geübt.
Am Beispiel von Iteratoren soll intuitiv klar werden, welchen Einfluss die Verwendung oder Nichtverwendung von inneren Klassen (speziell für Iteratoren) auf die Sichtbarkeit von Implementierungsdetails nach außen hat.
Was im Hinblick auf die Beurteilung zu beachten ist:
Der wichtigste Schwerpunkte bei der Beurteilung liegt auf der sinnvollen und korrekten Verwendung von Generizität.
Dementsprechend gibt es starke Punkteabzüge, wenn der Compiler mögliche Probleme im Zusammenhang mit Generizität meldet oder wichtige Teilaufgaben nicht gelöst oder umgangen werden.
Ein weiterer Schwerpunkt liegt auf dem gezielten Einsatz von Sichtbarkeit.
Es gibt Punkteabzüge, wenn Programmteile, die überall sichtbar sein sollten, nicht public sind, oder Teile, die nicht für die allgemeine Verwendung bestimmt sind, unnötig weit sichtbar sind.
Durch die Verwendung von inneren Klassen kann das Sichtbarmachen mancher Programmteile nach außen verhindert werden.
Weiters bilden Untertypbeziehungen (basierend auf Zusicherungen) einen Schwerpunkt.
Punkte werden abgezogen, wenn nicht richtig erkannt wurde, ob AnnotatedSet ein Untertyp von SortedSet ist, und wenn Zusicherungen Mängel aufweisen.
Vor allem Interfaces (aber auch Klassen) müssen mit informativen Zusicherungen versehen sein.
Der Begriff informativ
ist dabei nicht mit lang
, ausführlich
oder jede Trivialität beschreibend
zu verwechseln, sondern das Wesentliche soll kurz und bündig und möglichst eindeutig beschrieben sein - alles, was der Client über ein Service wissen muss, und alles, was der Server vom Client erwartet.
Generell führen Abänderungen der Aufgabenstellung (beispielsweise die Verwendung von Typumwandlungen oder von vorgefertigten Containern) zu Punkteabzügen.
Vermeiden die Verwendung von packages auch schon in Ihrem ersten Entwurf einer Lösung, da diese beim Übertragen auf die g0 immer wieder zu Problemen und damit zu leicht vermeidbaren Punkteabzügen führen (vergessene oder mehrfach vorkommende Dateien, package-Statements mit falschem Pfad, etc.).
Wie die Aufgabe zu lösen ist:
Diese Aufgabenstellung bietet wenig Interpretationsspielraum.
Wahrscheinlich können Sie ohne langwierige Analyse gleich mit dem Entwurf einer Lösung beginnen.
Schreiben Sie möglichst frühzeitig die Klasse
Test, da die Wahrscheinlichkeit groß ist, dass Sie beim Implementieren der vorgegebenen Testfälle Probleme in Ihrem Lösungsansatz finden.
Lassen Sie sich beim Entwurf der Lösung am besten von den Testfällen leiten.
Achten Sie besonders darauf, dass der Compiler keine Meldung ausgibt, die Sie möglicherweise nicht verstehen.
Sehr häufig haben solche Meldungen mit einer fehlerhaften Verwendung von Generizität (vor allem mit fehlenden Typparameterersetzungen) zu tun.
Verschleiern Sie keine Probleme im Umgang mit Generizität beispielsweise durch Typumwandlungen.
Achtung:
Es gibt noch immer Java-Compiler (vor allem unter Eclipse), die nicht alle Fehler im Zusammenhang mit Generizität richtig erkennen oder gar nicht mit Generizität umgehen können.
Verwenden Sie daher einen einigermaßen aktuellen Compiler und Testen Sie Ihre Lösung rechtzeitig auf der g0.
Halten Sie Ihr Programm so kurz wie möglich und vermeiden Sie mehrfache Implementierungen derselben Funktionalität.
Das ist sehr hilfreich, wenn es darum geht, den Überblick zu bewahren und Fehler zu vermeiden.