Grundlagen der Informatik I “Programmierung”
Grundlagen der Informatik I “Programmierung” Grundlagen der Informatik I “Programmierung”
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]
- Seite 75: wichtigste formale Sprache zur Besc
- Seite 78 und 79: Wir wollen jedoch deutlich darauf h
- Seite 80 und 81: Buch, sondern sollten besser als ei
- Seite 82 und 83: Viel sinnvoller ist es, bei der Bes
- Seite 84 und 85: Entsprechend ihrer beabsichtigten B
- Seite 86 und 87: Klassen sind rein statische Beschre
- Seite 88 und 89: leeren Verweis (d.h. von machen Per
- Seite 90 und 91: haben wie z.B. “(jahr:INTEGER)”
- Seite 92 und 93: und diese erzeugten Objekte mit Ver
- Seite 94 und 95: Bei der Erzeugung von Objekten mitt
- Seite 96 und 97: über den Namen eines Attributs Wer
- Seite 98 und 99: • Die Veränderung und Auswertung
- Seite 100 und 101: 3.5 Copy- und Referenz-Semantik In
- Seite 102 und 103: 3.5.2 expanded: Klassen mit Copy-Se
- Seite 104 und 105: class LIST[X] creation new feature
- Seite 106 und 107: Dieses Problem wird durch das Konze
- Seite 108 und 109: 3.7.1 Zusicherungen Eiffel logische
- Seite 110 und 111: class ARRAY[X] creation make featur
- Seite 112 und 113: class ARRAY[X] creation make featur
- Seite 114 und 115: formulieren und zu überwachen. Eig
- Seite 116 und 117: 3.8.2 Export geerbter Features Norm
- Seite 118 und 119: Ist also p ein Personenobjekt und a
- Seite 120 und 121: Definition 3.8.5 (Konformität) Ein
- Seite 122 und 123: Um die Beschreibung des Typsystems
- Seite 124 und 125: Nachkommenklassen aber folgt entity
- Seite 128 und 129: class STUDENT feature universität:
- Seite 130 und 131: Das Problem ist nun, daß beide Elt
- Seite 132 und 133: Prinzipiell wäre es sogar möglich
- Seite 134 und 135: Das bedeutet also, daß die Erbenkl
- Seite 136 und 137: Die eigentliche Montage eines Syste
- Seite 138 und 139: Verständlichkeit der Module: Die L
- Seite 140 und 141: dynamische Semantik: Die dynamische
- Seite 142 und 143: Constant ::= Manifest constant | Id
- Seite 145 und 146: Kapitel 4 Systematische Entwicklung
- Seite 147 und 148: 4.1.2 Grundideen des objektorientie
- Seite 149 und 150: jedoch dringend zu empfehlen, jegli
- Seite 151 und 152: • Ausleihe - Bücher werden nach
- Seite 153 und 154: Der Anwender ist kein echter Klient
- Seite 155 und 156: Es sei an dieser Stelle angemerkt,
- Seite 157 und 158: einen Rechner überprüft werden ka
- Seite 159 und 160: Definition 4.2.3 (Korrektheit von R
- Seite 161 und 162: Programmkonstruktion keine Rolle, d
- Seite 163 und 164: Eine Wertzuweisung entity := Ausdru
- Seite 165 und 166: 4.3.2.1 Die Rolle formaler Paramete
- Seite 167 und 168: Für eine korrekt implementierte Pr
- Seite 169 und 170: { pre} Anweisung1 { p} , { p} Anwei
- Seite 171 und 172: die alle dieselbe Variable x betref
- Seite 173 und 174: 4.3.4.4 Verifikation Die Verifikati
- Seite 175 und 176: Dieser Beweis ist in der folgenden
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]