Klassenschema: Unterschied zwischen den Versionen
Kowa (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Kowa (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
(16 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
=Informelle Definition (von W. Kowarschick)<ref> | {{Qualität | ||
[[Datei:UMLClassDetail.jpg|miniatur | |correctness = 3 | ||
|extent = 4 | |||
|numberOfReferences = 1 | |||
|qualityOfReferences = 4 | |||
|conformance = 5 | |||
}} | |||
==Informelle Definition (von W. Kowarschick)<ref>{{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</ref>== | |||
[[Datei:UMLClassDetail.jpg|miniatur|550px|UML-Klassendiagramm: Zwei Klassen und ein Klassentemplate deren Klassenschemata aus zahlreichen Integritätsbedingungen bestehen]] | |||
Ein [[Klassenschema]] (oder eine '''Klassenspezifikation''') definiert die [[Softwareschnittstelle]], die alle Objekte der zugehörigen [[Klasse (OOP)|Klasse]] mindestens unterstützen müssen. | Ein [[Klassenschema]] (oder eine '''Klassenspezifikation''') definiert die [[Softwareschnittstelle]], die alle Objekte der zugehörigen [[Klasse (OOP)|Klasse]] mindestens unterstützen müssen. | ||
=Definition (von W. Kowarschick)<ref> | ==Definition (von W. Kowarschick)<ref>{{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</ref>== | ||
Das zu einer [[Klasse (OOP)|Klasse]] gehörende [[Klassenschema]] umfasst eine (endliche) Menge von [[Integritätsbedingung]]en, die alle zugehörigen Objekte (d.h. alle in der [[Klassenextension|Extension]] | Das zu einer [[Klasse (OOP)|Klasse]] gehörende [[Klassenschema]] umfasst eine (endliche) Menge von [[Integritätsbedingung]]en, die alle zugehörigen Objekte (d.h. alle in der [[Klassenextension|Extension]] | ||
der zugehörigen Klasse enthaltenen Elemente) zu jedem Zeitpunkt erfüllen müssen. Das Schema [[deklariert]] insbesondere die [[Methoden]], die für jedes Objekt der Klassenextension jederzeit als [[Schnittstelle|Kommunikationsschnittstelle]] zur | der zugehörigen Klasse enthaltenen Elemente) zu jedem Zeitpunkt erfüllen müssen. Das Schema [[deklariert]] insbesondere die [[Methoden]], die für jedes Objekt der Klassenextension jederzeit als [[Schnittstelle|Kommunikationsschnittstelle]] zur Verfügung stehen müssen. | ||
=Bemerkungen= | ==Bemerkungen== | ||
Ein Klassenschema ist laut Definition nichts weiter als eine Menge von [[Integritätsbedingung]]en, die jedes Objekt erfüllen muss, dass der | Ein Klassenschema ist laut Definition nichts weiter als eine Menge von [[Integritätsbedingung]]en, die jedes Objekt erfüllen muss, dass der | ||
Zeile 21: | Zeile 29: | ||
*[[Klassenschema#Beziehungsbeschränkungen|Beziehungsbeschränkungen]] | *[[Klassenschema#Beziehungsbeschränkungen|Beziehungsbeschränkungen]] | ||
==Methoden-Signaturen== | ===Methoden-Signaturen=== | ||
Eine [[Signatur|Methoden-Signatur]] ist eine spezielle Integritätsbedingung, bestehend aus [[Zugriffsbeschränkung]]en (public, private, protected, internal etc.), [[Methode]]nnamen, [[Parameter|Eingabeparametern]] samt zugehörigen [[Datentyp]]en sowie den Datentypen der Methodenergebnisse. | Eine [[Signatur|Methoden-Signatur]] ist eine spezielle Integritätsbedingung, bestehend aus [[Zugriffsbeschränkung]]en (public, private, protected, internal etc.), [[Methode]]nnamen, [[Parameter|Eingabeparametern]] samt zugehörigen [[Datentyp]]en sowie den Datentypen der Methodenergebnisse. | ||
Zeile 28: | Zeile 36: | ||
Außerdem definiert jede Signatur eine [[Vorbedingung]] (zum Zeitpunkt des Methodenaufrufes müssen die [[Argument]]e den Bedingungen genügen, die | Außerdem definiert jede Signatur eine [[Vorbedingung]] (zum Zeitpunkt des Methodenaufrufes müssen die [[Argument]]e den Bedingungen genügen, die | ||
in der Signatur an die [[Parameter|Eingabeparameter]] gestellt werden) und eine [[Nachbedingung]] (die | in der Signatur an die [[Parameter|Eingabeparameter]] gestellt werden) und eine [[Nachbedingung]] (die Ergebnisse von Methodenaufrufen genügen den in der Signatur geforderten Ergebnisdatentypen). | ||
===Beispiel=== | ====Beispiel==== | ||
<source lang="actionscript3"> | <source lang="actionscript3"> | ||
public class Person | public class Person | ||
Zeile 124: | Zeile 132: | ||
</source> | </source> | ||
===Anmerkung=== | ====Anmerkung==== | ||
Im Falle von [[Methode]]n und [[Funktion]]en, die den Praogrammzustand nicht ändern, d.h. im Falle von [[Anfragemethode]]n oder [[seiteneffekt]]freien Funktionen kann man die Datentypen einer | Im Falle von [[Methode]]n und [[Funktion]]en, die den Praogrammzustand nicht ändern, d.h. im Falle von [[Anfragemethode]]n oder [[seiteneffekt]]freien Funktionen kann man die Datentypen einer | ||
Signatur nicht nur (so wie im obigen Beispiel) als Beschreibung einer [[Vorbedingung|Vor]]- und einer [[Nachbedingung]] auffassen, sondern auch als Beschreibung einer [[Invariante]]n. | Signatur nicht nur (so wie im obigen Beispiel) als Beschreibung einer [[Vorbedingung|Vor]]- und einer [[Nachbedingung]] auffassen, sondern auch als Beschreibung einer [[Invariante]]n. | ||
Zeile 131: | Zeile 139: | ||
Aus [[Imperative Programmierung|prozeduraler]] Sicht steht <math>A \times B</math> für folgende ''Vorbedingung'': | Aus [[Imperative Programmierung|prozeduraler]] Sicht steht <math>A \times B</math> für folgende ''Vorbedingung'': | ||
Vor Aufruf der Funktion/Methode <math>f\,</math> müssen je ein [[Objekt (OOP)|Objekt]]/[[Wert]] vom Typ <math>A\,</math> und vom Typ <math>B\,</math> auf den [[Programmstack]] | Vor Aufruf der Funktion/Methode <math>f\,</math> müssen je ein [[Objekt (OOP)|Objekt]]/[[Wert]] vom Typ <math>A\,</math> und vom Typ <math>B\,</math> der Funktion als aktuelle [[Argument]]e übergeben werden | ||
gelegt werden. Und <math>C\,</math> steht für folgende ''Nachbedingung'': | (z.B. indem diese Objekte in richtiger Reihenfolge auf den [[Programmstack]] gelegt werden). Und <math>C\,</math> steht für folgende ''Nachbedingung'': | ||
Nachdem die Funktion/Methode <math>f\,</math> bearbeitet wurde, | Nachdem die Funktion/Methode <math>f\,</math> bearbeitet wurde, liefert sie ein Objekt/Wert vom Typ <math>C\,</math> als Ergebnis (z.B., indem sie die Argumente vom Programmstack löscht und ein passendes | ||
Ergebnisobjekt auf den Programmstack ablegt). | |||
Aus [[Funktionale Programmierung|funktionaler Sicht]] steht die obige Signatur für folgende ''Invariante'':<br /> | Aus [[Funktionale Programmierung|funktionaler Sicht]] steht die obige Signatur für folgende ''Invariante'':<br /> | ||
< | <div class="formula">$\forall a \in A, b\in B: f(a,b) \in C$ oder auch $\forall a, b: a \in A \wedge b\in B \Rightarrow f(a,b) \in C$</div> | ||
Das heißt, <math>f\,</math> erfüllt folgende Bedingung '''dauerhaft''': Wenn <math>a\,</math> ein Element von <math>A\,</math> ist und <math>b\,</math> ein Element von <math>B\,</math>, dann ist | Das heißt, <math>f\,</math> erfüllt folgende Bedingung '''dauerhaft''': Wenn <math>a\,</math> ein Element von <math>A\,</math> ist und <math>b\,</math> ein Element von <math>B\,</math>, dann ist | ||
<math>f(a,b)\,</math> ein Element von <math>C\,</math>. | <math>f(a,b)\,</math> ein Element von <math>C\,</math>. | ||
Man kann diese Invariante noch um eine zweite Invariante ergänzen: | Man kann diese Invariante noch um eine zweite Invariante ergänzen: | ||
< | <div class="formula">$\forall a, b: a \not\in A \vee b\not\in B \Rightarrow f(a,b)$ throws error</div> | ||
Das heißt, wenn entweder <math>a\,</math> kein Element von <math>A\,</math> ist oder <math>b\,</math> kein Element von <math>B\,</math>, dann meldet | Das heißt, wenn entweder <math>a\,</math> kein Element von <math>A\,</math> ist oder <math>b\,</math> kein Element von <math>B\,</math>, dann meldet | ||
<math>f(a,b)\,</math> einen Fehler. | <math>f(a,b)\,</math> einen Fehler. | ||
Ob man die Datentypen einer Signatur eher als Vor- und Nachbedingung oder als | Ob man die Datentypen einer Signatur eher als Vor- und Nachbedingung oder als Invarianten auffasst, ist Geschmackssache. | ||
Wichtig ist, wie man sicherstellt, dass alle Integritätsverletzungen erkannt werden, um geeignete Gegenmaßnahmen zu | Wichtig ist, wie man sicherstellt, dass alle Integritätsverletzungen erkannt werden, um geeignete Gegenmaßnahmen zu | ||
ergreifen zu können. In einer Programmiersprache mit [[Starke Typisierung|starker Typisierung]] erkennt ein Compiler | ergreifen zu können. In einer Programmiersprache mit [[Starke Typisierung|starker Typisierung]] erkennt ein Compiler | ||
Zeile 152: | Zeile 161: | ||
Beispielsweise muss bei der Division | Beispielsweise muss bei der Division | ||
< | <source lang="actionscript3">operator/ (dividend: double, divisor: double\{0}): double</source> | ||
i. Allg. zur Laufzeit überprüft werden, ob die Bedingung <code>divisor!=0</code> erfüllt ist. | i. Allg. zur Laufzeit überprüft werden, ob die Bedingung <code>divisor!=0</code> erfüllt ist. | ||
==Zustandsvariablen== | ===Zustandsvariablen=== | ||
Die Deklaration einer [[Zustandsvariable]]n kann ebenfalls als Integritätsbedingung aufgefasst werden: | Die Deklaration einer [[Zustandsvariable]]n kann ebenfalls als Integritätsbedingung aufgefasst werden: | ||
Zeile 168: | Zeile 177: | ||
abgelegt werden dürfen. | abgelegt werden dürfen. | ||
===Anmerkung=== | ====Anmerkung==== | ||
In Sprachen wie Java, die keine Attribute unterstützen, werden öffentlich zugängliche Zustandsvariablen häufig als Attribute missbraucht. | In Sprachen wie Java, die keine Attribute unterstützen, werden öffentlich zugängliche Zustandsvariablen häufig als Attribute missbraucht. | ||
==Attribute== | ===Attribute=== | ||
Die Definition eines [[Attribut]]es, wie z.B. <code>birthday</code>, im Klassenschema | Die Definition eines [[Attribut]]es, wie z.B. <code>birthday</code>, im Klassenschema | ||
Zeile 194: | Zeile 203: | ||
des Konstruktors zu initialisieren und daher auf eine Setter-Methode zu verzichten. | des Konstruktors zu initialisieren und daher auf eine Setter-Methode zu verzichten. | ||
===Anmerkung=== | ====Anmerkung==== | ||
In Sprachen wie Java, die keine Attribute unterstützen, sollte man Methoden wie <code>getBirthday</code> und <code>setBirthday</code> | In Sprachen wie Java, die keine Attribute unterstützen, sollte man Methoden wie <code>getBirthday</code> und <code>setBirthday</code> | ||
zur Simulation von Getter- und Setter-Methoden verwenden. | zur Simulation von Getter- und Setter-Methoden verwenden. | ||
==Methoden-Constraints== | ===Methoden-Constraints=== | ||
Für Methoden können neben der Signatur weitere Invarianten sowie Vor- und Nachbedingungen spezifiziert werden. | Für Methoden können neben der Signatur weitere Invarianten sowie Vor- und Nachbedingungen spezifiziert werden. | ||
Zeile 234: | Zeile 243: | ||
</source> | </source> | ||
==Zustandsbeschränkungen== | ===Zustandsbeschränkungen=== | ||
Neben Methodensignaturen und -Constraints, Attributen und Zustandsvariablen können weitere Integritätsbedingungen im Klassenschema angegeben werden. | Neben Methodensignaturen und -Constraints, Attributen und Zustandsvariablen können weitere Integritätsbedingungen im Klassenschema angegeben werden. | ||
Zeile 246: | Zeile 255: | ||
</source> | </source> | ||
==Beziehungsbeschränkungen== | ===Beziehungsbeschränkungen=== | ||
[[Datei:UMLSubclassDetail.jpg|miniatur|rechts|650px|UML-Klassendiagramm: Eine Klassenhierachie mit Beziehung zu einer zweiten Klassenhierarchie]] | [[Datei:UMLSubclassDetail.jpg|miniatur|rechts|650px|UML-Klassendiagramm: Eine Klassenhierachie mit Beziehung zu einer zweiten Klassenhierarchie]] | ||
[[Beziehung]]en zwischen Klassen beschreiben ebenfalls Integritätsbedingungen. Allerdings gibt es mehrere Möglichkeiten, eine Beziehung | [[Beziehung]]en zwischen Klassen beschreiben ebenfalls Integritätsbedingungen. Allerdings gibt es mehrere Möglichkeiten, eine Beziehung | ||
Zeile 261: | Zeile 270: | ||
Die Menge muss (wegen der [[Vielfachheit]] <code>1..2</code>) ein bis zwei Objekte vom Typ <code>Engine</code> enthalten, z.B. einen Ottomotor und einen Elektromotor. | Die Menge muss (wegen der [[Vielfachheit]] <code>1..2</code>) ein bis zwei Objekte vom Typ <code>Engine</code> enthalten, z.B. einen Ottomotor und einen Elektromotor. | ||
===Anmerkung=== | ====Anmerkung==== | ||
Im nebenstehenden | Im nebenstehenden Diagramm sind zwei Arten von Beziehungen dargestellt. Eine Vererbungsbeziehung besteht nur zwischen Klassen, aber nicht zwischen den zugehörigen | ||
Objekten. Eine Part-of-Beziehung ([[Aggregation (OOP)|Aggregation]]) oder – allgemeiner – eine [[Assoziation]] besteht dageben zwischen Objekten. | Objekten. Eine Part-of-Beziehung ([[Aggregation (OOP)|Aggregation]]) oder – allgemeiner – eine [[Assoziation]] besteht dageben zwischen Objekten. | ||
Wenn eine derartige Beziehung in einem Klassendiagramm | Wenn eine derartige Beziehung in einem Klassendiagramm | ||
angegeben wird, beschreiben sie (als spezielle Integritätsbedingungen), welche Beziehungen zwischen den zugehörigen Objekten bestehen müssen. | angegeben wird, beschreiben sie (als spezielle Integritätsbedingungen), welche Beziehungen zwischen den zugehörigen Objekten bestehen müssen. | ||
Die Angabe von Vielfachheiten macht in einem [[Objekt-Diagramm]] keinen Sinn (daher werden insbesondere in einem Klassendiagramm bei einer Vererbungsbeziehung | Die Angabe von Vielfachheiten oder Multiplizitäten macht in einem [[Objekt-Diagramm]] keinen Sinn (daher werden insbesondere in einem Klassendiagramm bei einer Vererbungsbeziehung | ||
keine Vielfachheiten angegeben; hier werden die beiden beteiligten Klassen als Objekte einer Metaklasse <code>Class</code> aufgefasst). | keine Vielfachheiten angegeben; hier werden die beiden beteiligten Klassen als Objekte einer Metaklasse <code>Class</code> aufgefasst). | ||
Die Angabe von Vielfachheiten bei einer Assoziation in einen [[Klassendiagramm]] legt fest, | Die Angabe von Vielfachheiten bei einer Assoziation in einen [[Klassendiagramm]] legt fest, wie viele entsprechende Objektbeziehungen in einem zugehörigen Objektdiagramm | ||
existieren müssen. Eine Vielfachheit ist also ebenfalls eine spezielle Integritätsbedingung. | existieren müssen. Eine Vielfachheit ist also ebenfalls eine spezielle Integritätsbedingung. | ||
==Subklassen== | ===Subklassen=== | ||
[[Subklasse]]n erben laut Definition alle Integritätsbedingungen von ihrer Superklasse und können weitere Integritätsbedingungen hinzufügen. | [[Subklasse]]n erben laut Definition alle Integritätsbedingungen von ihrer Superklasse und können weitere Integritätsbedingungen hinzufügen. | ||
In der zuvor definierten Klassenhierachie erbt beispielsweise die Klasse <code>Car</code> alle Integritätsbedingungen der Klasse <code>Vehicle</code>. | In der zuvor definierten Klassenhierachie erbt beispielsweise die Klasse <code>Car</code> alle Integritätsbedingungen der Klasse <code>Vehicle</code>. | ||
Zeile 283: | Zeile 292: | ||
zusätzlich eine Ladefläche (<code>cargoSpace</code>) enthalten und zum anderen hat jeder Lastwagen (laut obiger Spezifikation!) genau einen Motor: | zusätzlich eine Ladefläche (<code>cargoSpace</code>) enthalten und zum anderen hat jeder Lastwagen (laut obiger Spezifikation!) genau einen Motor: | ||
Die Vielfachheit des geerbten Beziehungsattributs <code>engine</code> wird eingeschränkt. (Die Tatsache, dass diese Beziehung geerbt wurde, | Die Vielfachheit des geerbten Beziehungsattributs <code>engine</code> wird eingeschränkt. (Die Tatsache, dass diese Beziehung geerbt wurde, | ||
wir im UML-Diagramm mit einen | wir im UML-Diagramm mit einen Schrägstrich <code>/</code> vor dem Rollennamen symbolisiert.) | ||
===Anmerkung=== | ====Anmerkung==== | ||
Zusätzliche Integritätsbedingungen bedeuten immer Einschränkungen, das gilt auch für zusätzliche Attribute und Operationen. | Zusätzliche Integritätsbedingungen bedeuten immer Einschränkungen, das gilt auch für zusätzliche Attribute und Operationen. | ||
Wenn beispielsweise ein zusätzliches Attribut angegeben wird, bedeutet dies, dass jedes Objekt der Klassenextension dieses Attribut anbieten muss. | Wenn beispielsweise ein zusätzliches Attribut angegeben wird, bedeutet dies, dass jedes Objekt der Klassenextension dieses Attribut anbieten muss. | ||
Zeile 294: | Zeile 303: | ||
der Klasse <code>Car</code> für spezielle Autos verpflichtend eingeführt. | der Klasse <code>Car</code> für spezielle Autos verpflichtend eingeführt. | ||
=Quellen= | ==Quellen== | ||
<references/> | |||
==Siehe auch== | |||
#{{SieheAuch|Kowarschick (2002a)}} | |||
<noinclude>[[Kategorie:Objektorientierte Programmierung]] | <noinclude>[[Kategorie:Objektorientierte Programmierung]] | ||
[[Kategorie:Glossar | [[Kategorie:Glossar]] | ||
[[en:Class schema]] | [[en:Class schema]] | ||
[[Kategorie:Kapitel:Multimedia-Programmierung]] | [[Kategorie:Kapitel:Multimedia-Programmierung]] | ||
</noinclude> |
Aktuelle Version vom 16. Oktober 2019, 11:26 Uhr
Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:
Korrektheit: 3 (zu größeren Teilen überprüft) |
Umfang: 4 (unwichtige Fakten fehlen) |
Quellenangaben: 1 (fehlen großteils) |
Quellenarten: 4 (sehr gut) |
Konformität: 5 (ausgezeichnet) |
Informelle Definition (von W. Kowarschick)[1]
Ein Klassenschema (oder eine Klassenspezifikation) definiert die Softwareschnittstelle, die alle Objekte der zugehörigen Klasse mindestens unterstützen müssen.
Definition (von W. Kowarschick)[2]
Das zu einer Klasse gehörende Klassenschema umfasst eine (endliche) Menge von Integritätsbedingungen, die alle zugehörigen Objekte (d.h. alle in der Extension der zugehörigen Klasse enthaltenen Elemente) zu jedem Zeitpunkt erfüllen müssen. Das Schema deklariert insbesondere die Methoden, die für jedes Objekt der Klassenextension jederzeit als Kommunikationsschnittstelle zur Verfügung stehen müssen.
Bemerkungen
Ein Klassenschema ist laut Definition nichts weiter als eine Menge von Integritätsbedingungen, die jedes Objekt erfüllen muss, dass der zugehörigen Klasse angehört.
Es gibt eine ganze Reihe von Integritätsbedingungen, die ein Klassenschema enthalten kann. Die wichtigsten sind:
- Methoden-Signaturen
- Zustandsvariablen
- Attribute
- Methoden-Constraints
- Zustandsbeschränkungen
- Beziehungsbeschränkungen
Methoden-Signaturen
Eine Methoden-Signatur ist eine spezielle Integritätsbedingung, bestehend aus Zugriffsbeschränkungen (public, private, protected, internal etc.), Methodennamen, Eingabeparametern samt zugehörigen Datentypen sowie den Datentypen der Methodenergebnisse.
Das Vorhandensein einer Methoden-Signatur in einem Klassenschema definiert eine Invariante: Eine Methode, die dieser Signatur genügt, muss dauerhaft zur Kommunikation mit den Objekten der Klasse, die gemäß der in der Signatur angegebenen Zugriffsbeschränkung Zugriff haben, zur Verfügung stehen.
Außerdem definiert jede Signatur eine Vorbedingung (zum Zeitpunkt des Methodenaufrufes müssen die Argumente den Bedingungen genügen, die in der Signatur an die Eingabeparameter gestellt werden) und eine Nachbedingung (die Ergebnisse von Methodenaufrufen genügen den in der Signatur geforderten Ergebnisdatentypen).
Beispiel
public class Person
{
private var v_birthday: Date;
// Method "age"
public function age(p_date: Date = null): int
{
if (p_date == null)
p_date = new Date();
return ( (v_birthday.month > p_date.month)
|| ((v_birthday.month = p_date.month) &&
(v_birthday.date > p_date.date)
)
)
? p_date.fullYear - v_birthday.fullYear - 1
: p_date.fullYear - v_birthday.fullYear
}
// Constructor
public function Person(p_birthday: Date)
{
v_birthday = p_birthday;
}
}
Jedes Personenobjekt, d.h. jedes Element der Extension der Klasse Person
stellt eine Methode age
zur Verfügung,
public function age(p_date: Date = null): int
auf die jedes (public
) andere Objekt zugreifen darf:
var wolfgang: Person = new Person(new Date(1961, 5, 5));
trace(wolfgang.age());
trace(wolfgang.age(new Date(2011,5,2)));
trace(wolfgang.age(new Date(2011,5,11)));
Der Eingabeparameter muss nicht angegeben werden (Defaultwert null
), aber
falls er angegeben wird, muss er vom Typ Date
sein (Vorbedingung).
trace(wolfgang.age('2011-05-11')); // Fehler (zur Compilezeit),
// da die Vorbedingung
// „das Argument ist vom Typ Date“
// nicht erfüllt ist.
Die Methode age
liefert eine Integerzahl zurück (Nachbedingung).
Dass age
(als Nachbedingung!) das Alter der aktuellen Person zu einem bestimmten Datum bzw. zum
aktuellen Datum (falls p_date == null
) ermittelt, lässt sich der Signatur dagegen nicht entnehmen.
Hierfür wäre die Angabe von weiteren Integritätsbedingungen notwendig, was aber nur von wenigen Sprachen, wie z.B. Eiffel,
unterstützt wird. Eine weitere (deutlich schwächere) Nachbedingung wäre z.B., dass das Resultat positiv sein muss,
wenn das übergebene Datum p_date
größer ist, als das Geburtsdatum. Diese Bedingung wird z.B. verletzt,
wenn p_date
so groß gewählt wird, dass das Alter größer ist als die größte darstellbare Integerzahl.
Um gerade vor solchen Überraschungen gefeit zu sein, ist der Einsatz von Integritätsüberprüfungen,
die über die Angabe von Signatur-Informationen hinaus gehen, sehr empfehlenswert.
Bislang wurde gezeigt, dass die Signatur der Methode age
eine Vor- und eine Nachbedingung formuliert.
Sie formuliert allerdings auch eine Invariante: Die Methode age
existiert dauerhaft, d.h. sie kann nicht
entfernt oder verändert werden:
wolfgang.age = null; // Fehler (zur Compilezeit)
Dies ist nicht so selbstverständlich, wie es zunächst scheint. Wäre (in ActionScript) die Klasse Person als dynamisch deklariert worden
public dynamic class Person
{
...
}
so könnten jedem Personenobjekt jederzeit neue Methoden zugefügt werden. Und diese können auch wieder entfernt werden:
wolfgang.ageChristmas2011 = function (): int { return wolfgang.age(new Date(2011,12,24)); };
trace(wolfgang.ageChristmas2011());
wolfgang.age = null; // immer noch ein Fehler (zur Compilezeit)
wolfgang.ageChristmas2011 = null; // kein Fehler
trace(wolfgang.ageChristmas2011()); // jetzt ein Fehler (zur Laufzeit),
// ageChristmas2011 existiert nicht mehr
Anmerkung
Im Falle von Methoden und Funktionen, die den Praogrammzustand nicht ändern, d.h. im Falle von Anfragemethoden oder seiteneffektfreien Funktionen kann man die Datentypen einer Signatur nicht nur (so wie im obigen Beispiel) als Beschreibung einer Vor- und einer Nachbedingung auffassen, sondern auch als Beschreibung einer Invarianten.
Es sei $ f: A \times B \rightarrow C $ eine Signatur.
Aus prozeduraler Sicht steht $ A \times B $ für folgende Vorbedingung: Vor Aufruf der Funktion/Methode $ f\, $ müssen je ein Objekt/Wert vom Typ $ A\, $ und vom Typ $ B\, $ der Funktion als aktuelle Argumente übergeben werden (z.B. indem diese Objekte in richtiger Reihenfolge auf den Programmstack gelegt werden). Und $ C\, $ steht für folgende Nachbedingung: Nachdem die Funktion/Methode $ f\, $ bearbeitet wurde, liefert sie ein Objekt/Wert vom Typ $ C\, $ als Ergebnis (z.B., indem sie die Argumente vom Programmstack löscht und ein passendes Ergebnisobjekt auf den Programmstack ablegt).
Aus funktionaler Sicht steht die obige Signatur für folgende Invariante:
Das heißt, $ f\, $ erfüllt folgende Bedingung dauerhaft: Wenn $ a\, $ ein Element von $ A\, $ ist und $ b\, $ ein Element von $ B\, $, dann ist $ f(a,b)\, $ ein Element von $ C\, $.
Man kann diese Invariante noch um eine zweite Invariante ergänzen:
Das heißt, wenn entweder $ a\, $ kein Element von $ A\, $ ist oder $ b\, $ kein Element von $ B\, $, dann meldet $ f(a,b)\, $ einen Fehler.
Ob man die Datentypen einer Signatur eher als Vor- und Nachbedingung oder als Invarianten auffasst, ist Geschmackssache. Wichtig ist, wie man sicherstellt, dass alle Integritätsverletzungen erkannt werden, um geeignete Gegenmaßnahmen zu ergreifen zu können. In einer Programmiersprache mit starker Typisierung erkennt ein Compiler viele Verletzungen von Integritätsbedingungen schon zur Übersetzungszeit. Allerdings muss der Programmierer sogar in derartigen Sprachen häufig zur Laufzeit sicherstellen (z.B. mittels Assert-Befehlen), dass alle Bedinungen eingehalten werden. Beispielsweise muss bei der Division
operator/ (dividend: double, divisor: double\{0}): double
i. Allg. zur Laufzeit überprüft werden, ob die Bedingung divisor!=0
erfüllt ist.
Zustandsvariablen
Die Deklaration einer Zustandsvariablen kann ebenfalls als Integritätsbedingung aufgefasst werden:
public var birthday: Date;
Diese Deklaration legt beispielsweise fest, dass jedes (public
) Objekt jederzeit lesend und schreibend auf die Variable birthday
zugreifen darf. Dabei muss sie allerdings beachten, dass in dieser Zustandsvariablen nur Werte vom Typ Date
abgelegt werden dürfen.
Anmerkung
In Sprachen wie Java, die keine Attribute unterstützen, werden öffentlich zugängliche Zustandsvariablen häufig als Attribute missbraucht.
Attribute
Die Definition eines Attributes, wie z.B. birthday
, im Klassenschema
sollte als Definition von zwei speziellen Methoden aufgefasst werden
(deren Signaturen wiederum spezielle Integritätsbedingungen sind).
Zum einen gibt es eine Getter-Methode zum Lesen des Attributwertes:
public function get birthday(): Date
Zum anderen gibt es eine Setter-Methode zum Modifizieren des Attributwertes:
public function set birthday(p_birthday: Date): void
Bei Read-only-Attributen (Integritätsbedingung {read only}
) fehlt die Setter-Methode und
bei Add-only-Attributen (Integritätsbedingung {add only}
) fehlt die Getter-Methode.
Im Falle des Geburtstages ist es z.B. sinnvoll, das Geburtsdatum nur beim Erzeugen des Objektes mit Hilfe
des Konstruktors zu initialisieren und daher auf eine Setter-Methode zu verzichten.
Anmerkung
In Sprachen wie Java, die keine Attribute unterstützen, sollte man Methoden wie getBirthday
und setBirthday
zur Simulation von Getter- und Setter-Methoden verwenden.
Methoden-Constraints
Für Methoden können neben der Signatur weitere Invarianten sowie Vor- und Nachbedingungen spezifiziert werden.
Für die Modifikationsmethoden push
und pop
der Klasse Stack
wurden im obigen Klassendiagramm beispielsweise mehrere
Vor- und Nachbedingungen angegeben.
Nachdem ein Element auf den Stack gelegt wurde, ist dieser nicht leer und enthält ein Element mehr als zuvor:
{context push() post: !isEmpty }
{context push() post: size = size@pre+1}
Bevor ein Element vom Stack herunter geholt werden kann, muss dieser nicht leer sein. Danach enthält er ein Element weniger als zuvor.
{context pop() pre: !isEmpty}
{context pop() post: !size = size@pre-1}
Für die Anfragemethode age
der Klasse Person wurde die Semenatik mittels einer Invarianten spezifiziert.
Sofern eine Person im als Argument übergebenen Jahr d
noch nicht Geburtstag
gehabt hat, ermittelt man das Alter, indem man das Geburtsjahr und noch ein Jahr vom übergebenen Jahr abzieht.
Hatte die Person zum angegeben Zeitpunkt schon Geburtstag, muss lediglich das Geburtsjahr vom
übergebenen Jahr abgezogen werden.
{context d: Date
age(d) =
( self.birthday.month > d.month ||
(self.birthday.month = d.month && self.birthday.day > d.day)
)
? d.year-self.birthday.year-1
: d.year-self.birthday.year
}
Zustandsbeschränkungen
Neben Methodensignaturen und -Constraints, Attributen und Zustandsvariablen können weitere Integritätsbedingungen im Klassenschema angegeben werden. Man kann Integritätsbeziehungen formulieren, die die erlaubten Zustände der Objekte einschränken. Diese Integritätsbedingungen haben vor allem auf Attribute Auswirkungen.
Beispielsweise kann es keine Dreiecke mit den Seiten a
, b
und c
geben, bei denen die Summe von zwei Seiten kleiner ist, als die dritte Seite:
{a+b>c and b+c>a and c+a>b}
Beziehungsbeschränkungen
Beziehungen zwischen Klassen beschreiben ebenfalls Integritätsbedingungen. Allerdings gibt es mehrere Möglichkeiten, eine Beziehung zu implementieren. Zum einen kann dies durch eigenständige Beziehungsobjekte geschehen, zum anderen ist es auch möglich, Beziehungen als Attribute der beteiligten Klassen zu implementieren. Dabei erhält jede an der Beziehung beteiligte Klasse ein eigenes Attribut, sofern dies für bestimmte Klassen nicht explizit ausgeschlossen wird. Dieser Fall soll hier betrachtet werden.
Im Allgemeinen sind zwei Klassen an einer Beziehung beteiligt. In diesem Fall kann man bidirektionale und unidirektionale Beziehungen unterscheiden: Im ersten Fall erhalten beide beteiligte Klassen ein Beziehungsattribut, im zweiten Fall nur eine.
Im nebenstehenden Beispiel wurde zwischen Vehicle
und Engine
eine unidirektionale
(erkenntlich an der Pfeilspitze) Part-of-Beziehung (erkenntlich an der Raute)
engine
definiert. Dies bedeutet, dass der Klasse Vehicle
ein Attribut engine
vom Typ Set<Engine>
hinzugefügt werden muss.
Die Menge muss (wegen der Vielfachheit 1..2
) ein bis zwei Objekte vom Typ Engine
enthalten, z.B. einen Ottomotor und einen Elektromotor.
Anmerkung
Im nebenstehenden Diagramm sind zwei Arten von Beziehungen dargestellt. Eine Vererbungsbeziehung besteht nur zwischen Klassen, aber nicht zwischen den zugehörigen Objekten. Eine Part-of-Beziehung (Aggregation) oder – allgemeiner – eine Assoziation besteht dageben zwischen Objekten. Wenn eine derartige Beziehung in einem Klassendiagramm angegeben wird, beschreiben sie (als spezielle Integritätsbedingungen), welche Beziehungen zwischen den zugehörigen Objekten bestehen müssen.
Die Angabe von Vielfachheiten oder Multiplizitäten macht in einem Objekt-Diagramm keinen Sinn (daher werden insbesondere in einem Klassendiagramm bei einer Vererbungsbeziehung
keine Vielfachheiten angegeben; hier werden die beiden beteiligten Klassen als Objekte einer Metaklasse Class
aufgefasst).
Die Angabe von Vielfachheiten bei einer Assoziation in einen Klassendiagramm legt fest, wie viele entsprechende Objektbeziehungen in einem zugehörigen Objektdiagramm
existieren müssen. Eine Vielfachheit ist also ebenfalls eine spezielle Integritätsbedingung.
Subklassen
Subklassen erben laut Definition alle Integritätsbedingungen von ihrer Superklasse und können weitere Integritätsbedingungen hinzufügen.
In der zuvor definierten Klassenhierachie erbt beispielsweise die Klasse Car
alle Integritätsbedingungen der Klasse Vehicle
.
Für ein Auto gibt es also auch einen Hersteller (manufacturer
), ein zulässiges Gesamtgewicht (grossWeight
) das nicht gleich Null oder gar negativ sein darf, eine
Anzahl von Sitzplatzen seatingCapacity
sowie ein oder zwei Motoren (auch die Beziehung zur Klasse Engine
wird vererbt).
In der Klasse Car
wird eine weitere Integritätsbeziehung hinzugefügt: Die Anzahl der der Sitze darf höchstens acht betragen.
Auch in der Klasse Truck
werden weitere Integritätsbedingungen hinzugefügt: Zum einem muss jedes Objekt dieser Klasse
zusätzlich eine Ladefläche (cargoSpace
) enthalten und zum anderen hat jeder Lastwagen (laut obiger Spezifikation!) genau einen Motor:
Die Vielfachheit des geerbten Beziehungsattributs engine
wird eingeschränkt. (Die Tatsache, dass diese Beziehung geerbt wurde,
wir im UML-Diagramm mit einen Schrägstrich /
vor dem Rollennamen symbolisiert.)
Anmerkung
Zusätzliche Integritätsbedingungen bedeuten immer Einschränkungen, das gilt auch für zusätzliche Attribute und Operationen.
Wenn beispielsweise ein zusätzliches Attribut angegeben wird, bedeutet dies, dass jedes Objekt der Klassenextension dieses Attribut anbieten muss.
Fehlt die Angabe dieses Attributs in der Klassenextension, so heißt dies jedoch nicht, dass ein zugehöriges Objekt kein entsprechendes Attribut haben darf.
Beispielsweise muss jedes Objekt der Klasse Truck
ein Attribut cargoSpace
haben, Objekte der Klasse Vehicle
können dagegen so ein Attribut haben, müssen dies aber nicht. Ja sogar Autos (d.h. Objekte vom Typ Car
) könnten dieses Attribut haben.
Entweder wird es bei Bedarf dem ein oder anderen Auto dynamisch zur Laufzeit zugewiesen oder es wird in einer Subklasse (z.B. Van
)
der Klasse Car
für spezielle Autos verpflichtend eingeführt.
Quellen
- ↑ Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)
- ↑ Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)
Siehe auch
- Kowarschick (2002a): Wolfgang Kowarschick; Multimedia-Programmierung – Objektorientierte Grundlagen; Hrsg.: Michael Lutz und Christian Märtin; Reihe: Informatik interaktiv; Verlag: Fachbuchverlag Leipzig im Carl Hanser Verlag; ISBN: 3446217002; 2002; Quellengüte: 5 (Buch)