Grundlagen der Informatik I “Programmierung”
Grundlagen der Informatik I “Programmierung” Grundlagen der Informatik I “Programmierung”
Entsprechend ihrer beabsichtigten Bedeutung muß die Funktion empty eine Liste in einen booleschen Wert abbilden, die Funktion tail eine Liste in eine Liste und die Funktion head eine Liste in ein Element von X. Dabei ist jedoch zu berücksichtigen, daß die Funktion head nicht auf allen Listen definiert ist, also eine partielle Funktion ist. Dies wird in einem Teil der Literatur (zum Beispiel in [Meyer, 1988]) durch einen modifizierten Pfeil (→) besonders herausgehoben, obwohl dies wegen der Nennung von Vorbedingungen eigentlich überflüssig ist. Die Erzeugung einer neuen (leeren) Liste durch die Funktion new wird dadurch ausgedrückt, daß new keinen Argumenttyp besitzt und somit immer dasselbe Ergebnis liefert. new: → List[X] Aus der Sicht der Mathematik sind Funktionen ohne Argumente wie new (sogennante Konstruktor- Funktionen) und ihr Ergebnis (die Konstante ) praktisch identisch, da es von von außen betrachtet keine Rolle spielt, ob eine Dienstleistung zur Bereitstellung eines festen Wertes diesen erst berechnen muß oder auf einen bereits existierenden Wert zugreifen kann. Deshalb können alle Dienstleistungen als Funktionen beschrieben werden. Diese Sichtweise spiegelt sich auch in der Syntax der Sprache Eiffel wieder, welche die gleiche Philosophie verfolgt. • Partielle Funktionen sind in fast allen Programmierproblemen eine unausweichliche Tatsache, aber auch eine mögliche Quelle für Fehler. Aus diesem Grunde müssen die Anforderungen an die Anwendbarkeit jeder partiellen Funktion klar formuliert werden. Dies ist der Zweck des Abschnitts PRECONDITIONS. In unserem Beispiel ist die einzige Vorbedingung, daß head nur auf nichtleere Listen angewandt werden darf: pre head(L:List[X]) = (not empty(L)) • Der Abschnitt AXIOMS schließlich beschreibt die semantischen Mindestanforderungen, welche sicherstellen, daß die genannten Funktionen die gewünschten Eigenschaften besitzen. So soll die Funktion empty nur für die von new erzeugte leere Liste wahr sein, nicht aber bei Listen, die durch cons erzeugt wurden. head und tail zerlegen eine durch cons erzeugte Liste wieder in ihre Bestandteile während auf eine leere Liste nur tail anwendbar ist und wieder eine leere Liste liefert. Die abstrakte mathematische Beschreibung von Datentypen und Operationen in einem gemeinsamen Konzept erlaubt es, präzise Aussagen über die Effekte einer Berechnung aufzustellen und durch Verwendung der Axiome zu beweisen. Die Durchführung einer Berechnung kann nämlich als algebraische Vereinfachung (“Reduktion”) betrachtet werden, in der die linken (langen) Seiten eines Axioms durch die rechten (kurzen) ersetzt werden. So läßt sich zum Beispiel auch ohne Verwendung der anschaulich erklärten Bedeutung der Operationen tail, cons und new beweisen, daß das Ergebnis der Berechnung von tail(tail(cons(4,new()))) der Wert new() ist. Hierzu braucht man nur das vierte und dann das fünfte Axiom anzuwenden. Diese Aspekte abstrakter Datentypen haben sie zur Grundlage vieler Forschungen auf den Gebieten der formalen Spezifikation, der symbolischen Berechnung, der Verifikation von Programmen, dem Software-Prototyping und nicht zuletzt auch der Datenstrukturbeschreibung werden lassen. In objektorientierten Sprachen wie Eiffel wird daher jedes Programm um Klassen von Datenstrukturen herum organisiert, die auf der Grundlage abstrakter Datentypen beschrieben werden. Auf diese Art wird ein ausgewogenes Verhältnis zwischen Daten und Funktionen hergestellt. Die Struktur eines Softwaresystems beruht auf den Datenstrukturen, aber diese sind wiederum auf der Grundlage abstrakter Funktionen definiert: Objektorientierter Entwurf ist die Entwicklung von Softwaresystemen als strukturierte Sammlung von Implementierungen abstrakter Datentypen.
3.2.2 Klassen in Eiffel Mit den abstrakten Datentypen haben wir eine mathematische Beschreibungsform gegeben, die es erlaubt, Klassen von Objekten vollständig, genau und eindeutig zu charakterisieren und dabei gleichzeitig eine Überspezifikation zu vermeiden. Wir wollen nun zeigen, wie man eine solche Beschreibungsform in einer Programmiersprache ausdrücken kann. In der Denkweise der Programmiersprachen hat sich anstelle des Namens “abstrakter Datentyp” der Begriff “Klasse” zur Beschreibung der Gemeinsamkeiten einer Gruppe von Objekten eingebürgert. In Eiffel ist das Konzept der Klasse nahezu identisch mit dem der abstrakten Datentypen. Gegenüber der mathematischen Sichtweise ändern sich nur einige der verwendeten Begriffe und Schlüsselworte. Die einfachste Form einer Klassendefinition ist etwas ähnliches wie ein Verbundtyp in Pascal, in dem nur die gemeinsame Struktur einer Gruppe von Objekten festgelegt wird: class PERSON feature name, vornamen: STRING; geburtsjahr, todesjahr: INTEGER; nationalität: STRING end -- class PERSON Abbildung 3.5: Eine einfache Klassendefinition Die Klasse in Abbildung 3.5 beschreibt die Struktur einer Menge von Objekten, die Instanzen oder Exemplare dieser Klasse genannt werden. Die Klasse erhält den Namen PERSON und stellt Komponenten wie name, vorname, etc. zur Verfügung. Die Komponenten einer Klasse werden als Dienstleistungen der Klasse betrachtet, die für eventuelle Benutzer dieser Klasse nach außen hin verfügbar sind. Dabei spielt es – von außen betrachtet – keine Rolle, ob diese Dienstleistungen erbracht werden, indem auf eine abgespeicherte Komponente zugegriffen wird oder indem eine Berechnung (z.B. zur Bestimmung einer eindeutigen internen Kennung) durchgeführt wird. Diese Sicht deckt sich mit der im Abschnitt 3.2.1 angesprochenen Philosophie der abstrakten Datentypen, die alle Dienstleistungen als Funktionen ansieht, von denen einige vielleicht keine Eingaben benötigen. Um dieser Sichtweise Rechnung zu tragen, werden in Eiffel alle nach außen hin sichtbaren Dienstleistungen einer Klasse mit dem Oberbegriff feature 6 bezeichnet. Im einfachsten Fall sind Features nur Attribute, also Bestandteile von Klassen, die tatsächlich eine Komponente der Objekte der Klasse bezeichnen. Features können aber auch andere von der Klasse bereitgestellte Dienstleistungen sein, nämlich Routinen, die Operationen auf den Objekten der Klasse beschreiben, wie zum Beispiel die Bestimmung aller derzeit verfügbaren (nicht ausgeliehenen) Bücher, die von einem bestimmten Autor geschrieben wurden. Hierauf werden wir im Abschnitt 3.3 zurückkommen. Die Schlüsselworte class, feature und end, gedruckt in einem anderen Schriftsatz, werden zur syntaktischen Aufteilung einer Klassendefinition in Klassendeklaration und Deklaration von Features benutzt. Features vom selben Typ können in Deklaration gruppiert werden. Für die Aneinanderreihung von Deklarationen (und später auch von anderen Instruktionen) wird als Trennzeichen das Semikolon “;” verwendet und für Kommentare der doppelte Bindestrich “--”. Es hat sich als gute Konvention bewährt, am Ende einer Klassendefinition den Namen der Klasse im Kommentar zu wiederholen. Klassen sind die Grundkomponenten, aus denen alle Eiffel-Programme aufgebaut werden. Sie beschreiben nicht nur die Datenstrukturen, die in einem Softwareprodukt verwendet werden, sondern ebenso alle Module, aus denen dieses Softwareprodukt aufgebaut ist – einschließlich dem “Hauptprogramm”. Diese Vorgehensweise entspricht einer konsequenten Umsetzung der objektorientierten Sichtweise. Ein Programm ist nichts 6 Der deutsche Begriff für feature ist Merkmal. Der englische Begriff hat sich jedoch auch in der deutsprachigen Literatur eingebürgert, da er in seinem Verwendungszweck eindeutiger zu sein scheint. Wir werden dieser Tatsache Rechnung tragen und trotz des sich daraus ergebenden Mißbrauchs der deutschen Sprache und Grammatik den Begriff feature weiterverwenden.
- Seite 34 und 35: Durch das Konzept des Übersetzers
- Seite 36 und 37: und “örtlich” durch Zerlegung
- Seite 38 und 39: 1.3.4 Prozeduralisierung Das Beispi
- Seite 40 und 41: Man könnte das Objektkonzept von d
- Seite 43 und 44: Kapitel 2 Logik und formale Sprachb
- Seite 45 und 46: 2.1 Formale Sprachbeschreibungen Di
- Seite 47 und 48: möglichen Alternativen auf der rec
- Seite 49 und 50: Wenn wir eine Menge von Sprachen be
- Seite 51 und 52: Da wir im folgenden den Begriff der
- Seite 53 und 54: Für die Zuordnung zwischen der Obj
- Seite 55 und 56: Diese Form der Definition findet ih
- Seite 57 und 58: K1: Kommutativ-Gesetz E1 ∧E2 ≡
- Seite 59 und 60: Axiomenschemata: L1 A ∨ ¬A L2 (A
- Seite 61 und 62: In dem Kapitel über die Programmve
- Seite 63 und 64: 2. Es werden Quantoren eingeführt,
- Seite 65 und 66: s (T, state) = wahr s (F, state) =
- Seite 67 und 68: • x ist gebunden in p(t1, ..., tn
- Seite 69 und 70: Der Wert einer Aussage mit mehreren
- Seite 71 und 72: Wichtig ist, daß die Auswahl des W
- Seite 73 und 74: Wir geben hier eine Funktion an, di
- 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 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 126 und 127: deferred class LIST[X] feature leng
- 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
Entsprechend ihrer beabsichtigten Bedeutung muß die Funktion empty eine Liste in einen booleschen<br />
Wert abbilden, die Funktion tail eine Liste in eine Liste und die Funktion head eine Liste in ein Element<br />
von X. Dabei ist jedoch zu berücksichtigen, daß die Funktion head nicht auf allen Listen definiert ist,<br />
also eine partielle Funktion ist. Dies wird in einem Teil <strong>der</strong> Literatur (zum Beispiel in [Meyer, 1988])<br />
durch einen modifizierten Pfeil (→) beson<strong>der</strong>s herausgehoben, obwohl dies wegen <strong>der</strong> Nennung von<br />
Vorbedingungen eigentlich überflüssig ist.<br />
Die Erzeugung einer neuen (leeren) Liste durch die Funktion new wird dadurch ausgedrückt, daß new<br />
keinen Argumenttyp besitzt und somit immer dasselbe Ergebnis liefert.<br />
new: → List[X]<br />
Aus <strong>der</strong> Sicht <strong>der</strong> Mathematik sind Funktionen ohne Argumente wie new (sogennante Konstruktor-<br />
Funktionen) und ihr Ergebnis (die Konstante ) praktisch identisch, da es von von außen betrachtet<br />
keine Rolle spielt, ob eine Dienstleistung zur Bereitstellung eines festen Wertes diesen erst berechnen<br />
muß o<strong>der</strong> auf einen bereits existierenden Wert zugreifen kann. Deshalb können alle Dienstleistungen<br />
als Funktionen beschrieben werden. Diese Sichtweise spiegelt sich auch in <strong>der</strong> Syntax <strong>der</strong> Sprache Eiffel<br />
wie<strong>der</strong>, welche die gleiche Philosophie verfolgt.<br />
• Partielle Funktionen sind in fast allen Programmierproblemen eine unausweichliche Tatsache, aber auch<br />
eine mögliche Quelle für Fehler. Aus diesem Grunde müssen die Anfor<strong>der</strong>ungen an die Anwendbarkeit je<strong>der</strong><br />
partiellen Funktion klar formuliert werden. Dies ist <strong>der</strong> Zweck des Abschnitts PRECONDITIONS.<br />
In unserem Beispiel ist die einzige Vorbedingung, daß head nur auf nichtleere Listen angewandt werden<br />
darf:<br />
pre head(L:List[X]) = (not empty(L))<br />
• Der Abschnitt AXIOMS schließlich beschreibt die semantischen Mindestanfor<strong>der</strong>ungen, welche sicherstellen,<br />
daß die genannten Funktionen die gewünschten Eigenschaften besitzen.<br />
So soll die Funktion empty nur für die von new erzeugte leere Liste wahr sein, nicht aber bei Listen,<br />
die durch cons erzeugt wurden. head und tail zerlegen eine durch cons erzeugte Liste wie<strong>der</strong> in ihre<br />
Bestandteile während auf eine leere Liste nur tail anwendbar ist und wie<strong>der</strong> eine leere Liste liefert.<br />
Die abstrakte mathematische Beschreibung von Datentypen und Operationen in einem gemeinsamen Konzept<br />
erlaubt es, präzise Aussagen über die Effekte einer Berechnung aufzustellen und durch Verwendung <strong>der</strong> Axiome<br />
zu beweisen. Die Durchführung einer Berechnung kann nämlich als algebraische Vereinfachung (“Reduktion”)<br />
betrachtet werden, in <strong>der</strong> die linken (langen) Seiten eines Axioms durch die rechten (kurzen) ersetzt werden.<br />
So läßt sich zum Beispiel auch ohne Verwendung <strong>der</strong> anschaulich erklärten Bedeutung <strong>der</strong> Operationen tail,<br />
cons und new beweisen, daß das Ergebnis <strong>der</strong> Berechnung von tail(tail(cons(4,new()))) <strong>der</strong> Wert new()<br />
ist. Hierzu braucht man nur das vierte und dann das fünfte Axiom anzuwenden.<br />
Diese Aspekte abstrakter Datentypen haben sie zur Grundlage vieler Forschungen auf den Gebieten <strong>der</strong> formalen<br />
Spezifikation, <strong>der</strong> symbolischen Berechnung, <strong>der</strong> Verifikation von Programmen, dem Software-Prototyping<br />
und nicht zuletzt auch <strong>der</strong> Datenstrukturbeschreibung werden lassen. In objektorientierten Sprachen wie Eiffel<br />
wird daher jedes Programm um Klassen von Datenstrukturen herum organisiert, die auf <strong>der</strong> Grundlage<br />
abstrakter Datentypen beschrieben werden. Auf diese Art wird ein ausgewogenes Verhältnis zwischen Daten<br />
und Funktionen hergestellt. Die Struktur eines Softwaresystems beruht auf den Datenstrukturen, aber diese<br />
sind wie<strong>der</strong>um auf <strong>der</strong> Grundlage abstrakter Funktionen definiert:<br />
Objektorientierter Entwurf ist die Entwicklung von Softwaresystemen als strukturierte Sammlung<br />
von Implementierungen abstrakter Datentypen.