Grundlagen der Informatik I “Programmierung”

Grundlagen der Informatik I “Programmierung” Grundlagen der Informatik I “Programmierung”

22.08.2013 Aufrufe

deferred class LIST[X] feature length: INTEGER; empty: BOOLEAN is -- Ist die Liste leer ? do Result := length=0 end; new is -- Erzeuge leere Liste deferred ensure empty end; -- new cons(r:X) is -- Hänge r vor die Liste deferred ensure not empty; head = r -- tail würde jetzt old liefern end; -- cons head: X is -- Erstes Element require not empty deferred end; -- head tail is -- Entferne erstes Element deferred ensure old empty implies empty end -- tail invariant nonnegative size: size >= 0 end -- class LIST[X] Abbildung 3.35: Aufgeschobene Klasse Wir wollen aufgeschobene Klassen am Beispiel der generischen Klasse aller Listen illustrieren 30 , die wir bereits in Abbildung 3.4 als abstrakten Datentyp beschrieben und in Abbildung 3.26 als generische Klasse LIST skizziert hatten. Diese Klasse stellt Namen von Grundoperationen zur Verfügung, welche eine Art Mindestkollektion von Operationen auf Listen beschreiben. Eine aufgeschobene Klasse muß mit den Schlüsselworten deferred class beginnen, um klarzustellen, daß die Klasse nicht vollständig implementiert ist, da sie mindestens eine aufgeschobene Routine enthält. Dabei ist es durchaus erlaubt, daß einige Routinen einen vollständigen Anweisungsteil enthalten. Wir haben hierzu in Abbildung 3.35 die Klasse LIST um das feature length erweitert, welches eine einfache (und in allen Erben gültige) Implementierung der Funktion empty ermöglicht, die nicht aufgeschoben ist. Andere Routinen sind aufgeschoben, was man daran erkennt, daß anstelle des sonst üblichen mit do beginnenden Anweisungsteils das Schlüsselwort deferred steht. Alle anderen Bestandteile einer Routine – Kopf, Vor- und Nachbedingungen – bleiben erhalten . Man beachte, daß die aufgeschobene Klasse keine creation-Klausel hat, auch wenn die Routine new, die eigentlich dafür vorgesehen ist, deklariert wird. Die Begründung hierfür ist einfach: da Dienstleistungen einer aufgeschobenen Klasse eventuell nicht ausführbar sind, macht es wenig Sinn, Exemplare dieser Klasse zu erzeugen. 30 An dieser Stelle sei darauf hingewiesen, daß die in diesem Skript benutzte Klasse LIST dem Listenkonzept entspricht, das in der Mathematik und in funktionalen Programmiersprachen benutzt wird, aber nicht der gleichnamigen Klasse der Eiffel- Basisbibliothek (das habe ich leider erst entdeckt, nachdem die Hälfte des Skripts bereits geschrieben war). Die Eiffel-Klasse STACK kommt in ihrem Verwendungszweck den im Skript benutzten Listen am nächsten, verwendet jedoch andere Routinennamen. Für die Erklärung der Konzepte spielt dieser Unterschied keine Rolle, denn man hätte in der Eiffel-Basisbibliothek durchaus die hier verwandten Bezeichnungen benutzen können. Bitte berücksichtigen Sie jedoch bei Implementierungsarbeiten, daß der Compiler die Routinen new, head, tail, und cons in der Eiffel-Klasse LIST nicht finden wird.

Entwurfsprinzip 3.8.10 (Regel der Nicht-Erzeugung aufgeschobener Klassen) Auf Größen, deren Typ eine aufgeschobene Klasse ist, darf keine Initialisierungsprozedur angewandt werden. Beim ersten Hinsehen erscheint diese Regel sehr restriktiv und das wäre sie auch ohne Polymorphismus und dynamisches Binden. Hält man sich jedoch die oben genannten Anwendungen aufgeschobener Klassen vor Augen, so besagt diese Regel, daß Größen, deren Typ eine aufgeschobene Klasse ist, als Oberbegriff für Objekte der Erbenklasse zu verstehen sind. Die Größe ist polymorph, kann also Objekte verschiedener Klassen kennzeichnen und hierauf alle Routinen der Oberklasse anwenden. Sie ist allerdings kein Bezeichner für eine eigene Art von Objekten. Um also Objekte zu erzeugen, die mit dieser Größe angesprochen werden sollen, muß man festlegen, welche Art von Objekten erzeugt werden sollen und hierfür die Initialisierungsprozedur der entsprechenden Erbenklasse anwenden. Dies geschieht durch den bereits in Abschnitt 3.3.6 angesprochenen Aufruf !ERBEN KLASSE!entity bzw. !ERBEN KLASSE!entity.init(argumente) je nachdem ob die Erbenklasse eine spezifische Initialisierungsprozedur bereitstellt oder nicht. Da die Deklaration einer Routine als Initialisierungsprozedur sich aber nicht vererbt sondern lokal in jeder Klasse stattfinden muß (vgl. Abschnitt 3.8.1), braucht man in aufgeschobenen Klassen auch keine Initialisierungsprozedur zu deklarieren. Nachkommen einer aufgeschobenen Klasse können nun effektive Versionen einer aufgeschobenen Routine anbieten 31 . Da für diese Routine bisher keine Anweisungsfolge bekannt war, gilt sie auch nicht als redefiniert. Die Erbenklasse muß sie also nicht in einer redefine-Klausel aufführen (vergleiche die Regel 3.8.4 der Redefinition). Natürlich muß die Implementierung die Vor- und Nachbedingungen einhalten, die in der Ahnenklasse vereinbart wurden. 3.8.7 Mehrfachvererbung In den bisherigen Beispielen hatten Erben immer nur ein Elternteil. Dies muß aber nicht immer der Fall sein. So sind zum Beispiel studentische Hilfskräfte gleichzeitig Studenten einer bestimmten Universität und Arbeitnehmer. Die Klasse HILFSKRAFT erbt also Eigenschaften von zwei Klassen, nämlich STUDENT und ARBEITNEHMER. ✬ ✫ STUDENT ✩ ❍❨ ❍✪ ❍ ❍ ❍✬ HILFSKRAFT ✫ ✟✩✟✟✟✟✯ ✫ ✪ ✬ ARBEITNEHMER ✩ ✪ Abbildung 3.36: Mehrfachvererbung als Diagramm: HILFSKRAFT erbt von STUDENT und ARBEITNEHMER Mehrfachvererbung läßt sich in Eiffel sehr leicht ausdrücken. In der Vererbungsklausel werden einfach mehrere Elternklassen zusammen mit den eventuell notwendigen Modifikationen geerbter features angegeben. Die Möglichkeit mehrere Elternklassen anzugeben erklärt auch die Verwendung des Schlüsselwortes end am Ende der feature-Anpassung einer Elternklasse. Abbildung 3.37 zeigt die Deklaration einer Klasse HILFSKRAFT, die als direkter Nachkomme der Klassen STUDENT und ARBEITNEHMER erklärt ist. 31 Der Vollständigkeit halber sei auch erwähnt, daß man umgekehrt auch eine effektive Version einer Routine in eine aufgeschobene Routine umwandeln kann. Dies geschieht durch Verwendung einer Unterklausel in der Erbklausel, die mit dem Schlüsselwort undefine beginnt. Details findet man in [Meyer, 1992, Kapitel 10.16]

Entwurfsprinzip 3.8.10 (Regel <strong>der</strong> Nicht-Erzeugung aufgeschobener Klassen)<br />

Auf Größen, <strong>der</strong>en Typ eine aufgeschobene Klasse ist, darf keine Initialisierungsprozedur angewandt<br />

werden.<br />

Beim ersten Hinsehen erscheint diese Regel sehr restriktiv und das wäre sie auch ohne Polymorphismus<br />

und dynamisches Binden. Hält man sich jedoch die oben genannten Anwendungen aufgeschobener Klassen<br />

vor Augen, so besagt diese Regel, daß Größen, <strong>der</strong>en Typ eine aufgeschobene Klasse ist, als Oberbegriff für<br />

Objekte <strong>der</strong> Erbenklasse zu verstehen sind. Die Größe ist polymorph, kann also Objekte verschiedener Klassen<br />

kennzeichnen und hierauf alle Routinen <strong>der</strong> Oberklasse anwenden.<br />

Sie ist allerdings kein Bezeichner für eine eigene Art von Objekten. Um also Objekte zu erzeugen, die mit<br />

dieser Größe angesprochen werden sollen, muß man festlegen, welche Art von Objekten erzeugt werden sollen<br />

und hierfür die Initialisierungsprozedur <strong>der</strong> entsprechenden Erbenklasse anwenden. Dies geschieht durch den<br />

bereits in Abschnitt 3.3.6 angesprochenen Aufruf<br />

!ERBEN KLASSE!entity bzw. !ERBEN KLASSE!entity.init(argumente)<br />

je nachdem ob die Erbenklasse eine spezifische Initialisierungsprozedur bereitstellt o<strong>der</strong> nicht. Da die Deklaration<br />

einer Routine als Initialisierungsprozedur sich aber nicht vererbt son<strong>der</strong>n lokal in je<strong>der</strong> Klasse stattfinden<br />

muß (vgl. Abschnitt 3.8.1), braucht man in aufgeschobenen Klassen auch keine Initialisierungsprozedur zu<br />

deklarieren.<br />

Nachkommen einer aufgeschobenen Klasse können nun effektive Versionen einer aufgeschobenen Routine<br />

anbieten 31 . Da für diese Routine bisher keine Anweisungsfolge bekannt war, gilt sie auch nicht als redefiniert.<br />

Die Erbenklasse muß sie also nicht in einer redefine-Klausel aufführen (vergleiche die Regel 3.8.4 <strong>der</strong> Redefinition).<br />

Natürlich muß die Implementierung die Vor- und Nachbedingungen einhalten, die in <strong>der</strong> Ahnenklasse<br />

vereinbart wurden.<br />

3.8.7 Mehrfachvererbung<br />

In den bisherigen Beispielen hatten Erben immer nur ein Elternteil. Dies muß aber nicht immer <strong>der</strong> Fall sein. So<br />

sind zum Beispiel studentische Hilfskräfte gleichzeitig Studenten einer bestimmten Universität und Arbeitnehmer.<br />

Die Klasse HILFSKRAFT erbt also Eigenschaften von zwei Klassen, nämlich STUDENT und ARBEITNEHMER.<br />

✬<br />

✫<br />

STUDENT<br />

✩<br />

❍❨ ❍✪<br />

❍<br />

❍<br />

❍✬<br />

HILFSKRAFT<br />

✫<br />

✟✩✟✟✟✟✯ ✫<br />

✪<br />

✬<br />

ARBEITNEHMER<br />

✩<br />

✪<br />

Abbildung 3.36: Mehrfachvererbung als Diagramm: HILFSKRAFT erbt von STUDENT und ARBEITNEHMER<br />

Mehrfachvererbung läßt sich in Eiffel sehr leicht ausdrücken. In <strong>der</strong> Vererbungsklausel werden einfach mehrere<br />

Elternklassen zusammen mit den eventuell notwendigen Modifikationen geerbter features angegeben. Die<br />

Möglichkeit mehrere Elternklassen anzugeben erklärt auch die Verwendung des Schlüsselwortes end am Ende<br />

<strong>der</strong> feature-Anpassung einer Elternklasse. Abbildung 3.37 zeigt die Deklaration einer Klasse HILFSKRAFT, die<br />

als direkter Nachkomme <strong>der</strong> Klassen STUDENT und ARBEITNEHMER erklärt ist.<br />

31 Der Vollständigkeit halber sei auch erwähnt, daß man umgekehrt auch eine effektive Version einer Routine in eine aufgeschobene<br />

Routine umwandeln kann. Dies geschieht durch Verwendung einer Unterklausel in <strong>der</strong> Erbklausel, die mit dem Schlüsselwort<br />

undefine beginnt. Details findet man in [Meyer, 1992, Kapitel 10.16]

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!