Grundlagen der Informatik I “Programmierung”

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

22.08.2013 Aufrufe

Ist also p ein Personenobjekt und a ein Arbeitnehmerobjekt, so ist der Aufruf a.name, a.vornamen genauso erlaubt wie p.name, p.vornamen. Darüber hinaus kann man allerdings auch a.adresse und a.gehalt aufrufen, nicht jedoch p.adresse oder p.gehalt Dies gilt natürlich nur dann, wenn die geerbtes features nicht durch export-Klauseln von der Weiterverwendung ausgeschlossen werden. Diese Eigenschaft läßt sich als eine erste wichtige Regel des Typsystems von Eiffel beschreiben. Entwurfsprinzip 3.8.2 (Regel der Featureanwendung) Ist eine Größe entity vom Klassentyp K, so ist die Anwendung entity.f eines feature f nur dann zulässig, wenn f in einem Vorfahren von K definiert wurde. Man beachte, daß diese Regel “nur dann” und nicht etwa “genau dann” besagt. Die Voraussetzung, daß ein feature in einem Vorfahren von K (d.h. eventuell auch in K selbst) definiert wurde, ist notwendig für eine Anwendung, reicht aber nicht immer aus – zum Beispiel, weil das feature durch die Klasse K nicht oder nur selektiv exportiert wurde. Die Interpretation des selektiven Exports von features wird allerdings durch die Vererbung etwas aufgeweicht. Da Erben alle Rechte ihrer Vorfahren besitzten, haben sie natürlich auch alle Zugriffsrechte. Das bedeutet, daß ein selektiv an eine Klasse exportiertes feature auch innerhalb aller Erbenklassen zur Verfügung steht – selbst, wenn diese nicht explizit in der export- bzw. feature-Klausel genannt sind. Entwurfsprinzip 3.8.3 (Regel des selektiven Exports) Ein an eine Klasse K selektiv exportiertes feature f darf in allen Nachkommen von K benutzt werden. In den folgenden Abschnitten werden wir weitere Regeln des Typsystems von Eiffel vorstellen. All diese Regeln sind statischer Natur, d.h. sie können ausschließlich auf der Basis des Programmtextes überprüft werden 23 . Der Compiler wird Klassen abweisen, die eine dieser Regeln verletzen. Auf diese Art wird sichergestellt, daß es während der Laufzeit eines Systems nicht mehr zu Typfehlern kommen kann. 3.8.3 Redefinition So wie es Gründe dafür gibt, geerbte features selektiv an verschiedene Klassen zu exportieren, gibt es auch Gründe, ihre Implementierung zu verändern. So wird zum Beispiel bei Universitätsangestellten das Gehalt nicht alleine aufgrund der Gehaltsklasse bestimmt, sondern hängt zusätzlich vom tatsächlichen Alter der Angestellten ab ab. Das bedeutet, daß die Funktion gehalt bei Universitätsangestellten nach einem anderen Verfahren berechnet werden sollte als im Allgemeinfall. Um dies zu ermöglichen, erlaubt Eiffel, geerbte features neu zu definieren. Hierzu wird in einer redefine Klausel angegeben, welche features verändert werden. Natürlich muß jetzt jedes dieser features erneut als feature der Klasse deklariert und implementiert werden. Abbildung 3.32 enthält eine Deklaration der Klasse aller Universitätsangestellten, in der das feature gehalt neudefiniert wurde. Ohne die redefine-Klausel wäre die erneute Deklaration von gehalt als Merkmal von UNI-ANGESTELLTE ein Fehler, da gehalt bereits aus der Klasse ARBEITNEHMER übernommen wird. Die redefine-Klausel teilt mit, welche features einer Elternklasse in einer Erbenklasse neudefiniert werden 24 . Hierdurch wird gewährleistet, daß derselbe Name sich auf verschiedene aktuelle features beziehen kann. Abhängig vom Typ des Objektes, auf das es angewandt wird, erhält man eine andere Implementierung. 23 Für fast alle Regeln gibt es im Referenzbuch [Meyer, 1992] verfeinerte Formen. Diese behandeln jedoch auch alle Spezialfälle, die sich aus Kombinationen diverser Möglichkeiten ergeben und sind zur Erklärung des eigentlichen Prinzip ungeeignet. 24 Der Compiler benötigt die redefine-Klausel eigentlich nicht, weil jede erneute Deklaration eines ererbten features ja nur eine Redefinition sein kann, sofern kein Fehler des Programmierers vorliegt. Da aber irrtümliche Redefinitionen bei umfangreichen Ahnenklassen nicht unbedingt ausgeschlossen werden können, muß Redefinition ausdrücklich als solche gekennzeichnet werden. Dies erhöht die Sicherheit und Lesbarkeit der Programme. Es sei an dieser Stelle auch erwähnt, daß ein feature durch das Schlüsselwort frozen gegen Redefinition sperren kann, wenn man darauf Wert legt, allen Benutzen eine eine unveränderliche Implementierung bereitzustellen.

class UNI-ANGESTELLTE inherit ARBEITNEHMER redefine gehalt export {ARBEITGEBER} name, vorname, geburtsjahr, adresse, gehaltsklasse; {FINANZAMT} all end -- feature-Anpassung von ARBEITNEHMER feature universität: STRING; fachbereich, raum, telephon: INTEGER; gehalt: REAL is -- Gehalt berechnen do Result := Gehalt nach Alter und gehaltsklasse end end -- class UNI-ANGESTELLTE Abbildung 3.32: Vererbung mit Redefinition Natürlich kann man diese nicht beliebig abändern. Von außen betrachtet muß ein feature im wesentlichen unverändert, d.h. auf die gleiche Art aufrufbar und veränderbar bleiben. Kurzum, die Vereinbarungen der Elternklasse müssen weiterhin Gültigkeit haben – ansonsten sollte man besser ein neues feature deklarieren. Zu den Vereinbarungen, die von der Erbenklasse übernommen werden müssen, gehören insbesondere die Anzahl der Argumente und die Typen der features. Routinen müssen Routinen bleiben und Attribute Attribute. Die einzigen erlaubten Ausnahmen sind ein Wechsel von parameterlosen Funktionen zu Attributen – weil hier der Kunde den Unterschied nicht sieht – und ein Übergang zu Typen, die zum ursprünglichen Datentyp passen (“konform sind”). Entwurfsprinzip 3.8.4 (Regel der Redefinition) Ein in einer Klasse deklariertes Attribut, Funktionsergebnis oder formales Routinenargument darf in einer Erbenklasse mit einem neuen Typ redeklariert werden, wenn der neue Typ konform zum ursprünglichen ist. Das Attribut oder die zugehörige Routine gilt als redefiniert. Der Rumpf einer Routine kann redefiniert werden, solange die obige Typeinschränkung nicht verletzt wird. Parameterlose Funktionen dürfen dabei als Attribute redefiniert werden. Redefinierte features, die nicht ursprünglich deferred (siehe Abschnitt 3.8.6) waren, müssen in einer entsprechenden redefine Klausel aufgeführt werden. Die Idee hinter dieser Regelung ist, daß eine Klasse immer eine speziellere Version eines in einer Ahnenklasse deklarierten Elementes anbieten kann. Deshalb ist auch eine Redefinition eines Attributes als parameterlose Funktion nicht erlaubt, weil hierdurch die Zuweisung von Werten an das Attribut unmöglich gemacht wird 25 . Die Redefinitionsregel verwendet den Begriff der Konformität zwischen zwei Datentypen. “B ist konform zu A” bedeutet in erster Näherung, daß B ein Nachkomme von A sein muß. Dies gilt in ähnlicher Form auch für den Fall, daß A und B generische Parameter enthalten. Alle in B vorkommenden aktuellen generischen Parameter müssen Nachkommen der entsprechenden Parameter in A sein. So kann zum Beispiel B die Klasse TWO WAY LIST[ENTLEIHER] sein und A die Klasse LINKED LIST[PERSON], denn die generische Klasse TWO WAY LIST ist Nachkomme von LINKED LIST und der aktuelle Parameter ENTLEIHER ein Nachkomme von PERSON. Die präzise Definition ist etwas komplizierter, da B auch in der Vererbungsklausel eine Klasse mit generischen Parametern nennen kann, die A als Vorfahren besitzt, das Konzept der Deklaration durch Assoziation hinzukommt, das wir erst in Abschnitt 3.8.5 besprechen werden, und Konformität transitiv ist. 25 Bei einer Redefinition eines Attributes als parameterlose Funktion dürfte auch die Ahnenklasse keine Zuweisung an das Attribut mehr enthalten, da Größen dieser Klasse ja auch Objekte einer Nachkommenklasse bezeichnen können – Personengrößen dürfen zum Beispiel auch auf spezielle Personen, nämlich Entleiher zeigen. Damit würde die Redefinition aber einen Eingriff in die Elternklasse mit sich bringen, was völlig gegen den Sinn der Strukturierung eines Systems in unabhängige Klassen ist.

Ist also p ein Personenobjekt und a ein Arbeitnehmerobjekt, so ist <strong>der</strong> Aufruf a.name, a.vornamen<br />

genauso erlaubt wie p.name, p.vornamen. Darüber hinaus kann man allerdings auch a.adresse<br />

und a.gehalt aufrufen, nicht jedoch p.adresse o<strong>der</strong> p.gehalt<br />

Dies gilt natürlich nur dann, wenn die geerbtes features nicht durch export-Klauseln von <strong>der</strong><br />

Weiterverwendung ausgeschlossen werden.<br />

Diese Eigenschaft läßt sich als eine erste wichtige Regel des Typsystems von Eiffel beschreiben.<br />

Entwurfsprinzip 3.8.2 (Regel <strong>der</strong> Featureanwendung)<br />

Ist eine Größe entity vom Klassentyp K, so ist die Anwendung entity.f eines feature f nur dann<br />

zulässig, wenn f in einem Vorfahren von K definiert wurde.<br />

Man beachte, daß diese Regel “nur dann” und nicht etwa “genau dann” besagt. Die Voraussetzung, daß ein<br />

feature in einem Vorfahren von K (d.h. eventuell auch in K selbst) definiert wurde, ist notwendig für eine<br />

Anwendung, reicht aber nicht immer aus – zum Beispiel, weil das feature durch die Klasse K nicht o<strong>der</strong> nur<br />

selektiv exportiert wurde.<br />

Die Interpretation des selektiven Exports von features wird allerdings durch die Vererbung etwas aufgeweicht.<br />

Da Erben alle Rechte ihrer Vorfahren besitzten, haben sie natürlich auch alle Zugriffsrechte. Das bedeutet,<br />

daß ein selektiv an eine Klasse exportiertes feature auch innerhalb aller Erbenklassen zur Verfügung steht –<br />

selbst, wenn diese nicht explizit in <strong>der</strong> export- bzw. feature-Klausel genannt sind.<br />

Entwurfsprinzip 3.8.3 (Regel des selektiven Exports)<br />

Ein an eine Klasse K selektiv exportiertes feature f darf in allen Nachkommen von K benutzt werden.<br />

In den folgenden Abschnitten werden wir weitere Regeln des Typsystems von Eiffel vorstellen. All diese Regeln<br />

sind statischer Natur, d.h. sie können ausschließlich auf <strong>der</strong> Basis des Programmtextes überprüft werden 23 .<br />

Der Compiler wird Klassen abweisen, die eine dieser Regeln verletzen. Auf diese Art wird sichergestellt, daß<br />

es während <strong>der</strong> Laufzeit eines Systems nicht mehr zu Typfehlern kommen kann.<br />

3.8.3 Redefinition<br />

So wie es Gründe dafür gibt, geerbte features selektiv an verschiedene Klassen zu exportieren, gibt es auch<br />

Gründe, ihre Implementierung zu verän<strong>der</strong>n. So wird zum Beispiel bei Universitätsangestellten das Gehalt<br />

nicht alleine aufgrund <strong>der</strong> Gehaltsklasse bestimmt, son<strong>der</strong>n hängt zusätzlich vom tatsächlichen Alter <strong>der</strong><br />

Angestellten ab ab. Das bedeutet, daß die Funktion gehalt bei Universitätsangestellten nach einem an<strong>der</strong>en<br />

Verfahren berechnet werden sollte als im Allgemeinfall. Um dies zu ermöglichen, erlaubt Eiffel, geerbte features<br />

neu zu definieren. Hierzu wird in einer redefine Klausel angegeben, welche features verän<strong>der</strong>t werden. Natürlich<br />

muß jetzt jedes dieser features erneut als feature <strong>der</strong> Klasse deklariert und implementiert werden.<br />

Abbildung 3.32 enthält eine Deklaration <strong>der</strong> Klasse aller Universitätsangestellten, in <strong>der</strong> das feature gehalt<br />

neudefiniert wurde. Ohne die redefine-Klausel wäre die erneute Deklaration von gehalt als Merkmal von<br />

UNI-ANGESTELLTE ein Fehler, da gehalt bereits aus <strong>der</strong> Klasse ARBEITNEHMER übernommen wird.<br />

Die redefine-Klausel teilt mit, welche features einer Elternklasse in einer Erbenklasse neudefiniert werden 24 .<br />

Hierdurch wird gewährleistet, daß <strong>der</strong>selbe Name sich auf verschiedene aktuelle features beziehen kann.<br />

Abhängig vom Typ des Objektes, auf das es angewandt wird, erhält man eine an<strong>der</strong>e Implementierung.<br />

23 Für fast alle Regeln gibt es im Referenzbuch [Meyer, 1992] verfeinerte Formen. Diese behandeln jedoch auch alle Spezialfälle,<br />

die sich aus Kombinationen diverser Möglichkeiten ergeben und sind zur Erklärung des eigentlichen Prinzip ungeeignet.<br />

24 Der Compiler benötigt die redefine-Klausel eigentlich nicht, weil jede erneute Deklaration eines ererbten features ja nur eine<br />

Redefinition sein kann, sofern kein Fehler des Programmierers vorliegt. Da aber irrtümliche Redefinitionen bei umfangreichen<br />

Ahnenklassen nicht unbedingt ausgeschlossen werden können, muß Redefinition ausdrücklich als solche gekennzeichnet werden.<br />

Dies erhöht die Sicherheit und Lesbarkeit <strong>der</strong> Programme.<br />

Es sei an dieser Stelle auch erwähnt, daß ein feature durch das Schlüsselwort frozen gegen Redefinition sperren kann, wenn<br />

man darauf Wert legt, allen Benutzen eine eine unverän<strong>der</strong>liche Implementierung bereitzustellen.

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

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!