Klassenschema: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Wechseln zu:Navigation, Suche
(Zustandsbeschränkungen)
(Bemerkungen)
Zeile 17: Zeile 17:
 
*[[Klassenschema#Zustandsvariablen|Zustandsvariablen]]  
 
*[[Klassenschema#Zustandsvariablen|Zustandsvariablen]]  
 
*[[Klassenschema#Attribute|Attribute]]  
 
*[[Klassenschema#Attribute|Attribute]]  
 +
*[[Klassenschema#Methoden-Constraints|Methoden-Constraints]]
 
*[[Klassenschema#Zustandsbeschränkungen|Zustandsbeschränkungen]]  
 
*[[Klassenschema#Zustandsbeschränkungen|Zustandsbeschränkungen]]  
 
*[[Klassenschema#Beziehungsbeschränkungen|Beziehungsbeschränkungen]]  
 
*[[Klassenschema#Beziehungsbeschränkungen|Beziehungsbeschränkungen]]  

Version vom 1. März 2011, 18:03 Uhr

1 Informelle Definition (von W. Kowarschick)[1]

miniatur|rechts|550px|UML-Klassendiagramm: Eine Klasse und ein Klassen-Template Ein Klassenschema (oder eine Klassenspezifikation) definiert die Softwareschnittstelle, die alle Objekte der zugehörigen Klasse mindestens unterstützen müssen.

2 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ügungstehen müssen.

3 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:

3.1 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 Methodernergebnisse genügen den in der Signatur geforderten Ergebnisdatentypen).

3.1.1 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

3.1.2 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 [math]f: A \times B \rightarrow C[/math] eine Signatur.

Aus 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/Wert vom Typ [math]A\,[/math] und vom Typ [math]B\,[/math] auf den Programmstack gelegt werden. Und [math]C\,[/math] steht für folgende Nachbedingung: Nachdem die Funktion/Methode [math]f\,[/math] bearbeitet wurde, findet sich ein Objekt/Wert vom Typ [math]C\,[/math] auf dem Programmstack.

Aus funktionaler Sicht steht die obige Signatur für folgende Invariante:
[math]\forall a \in A, b\in B: f(a,b) \in C[/math] oder auch [math]\forall a, b: a \in A \wedge b\in B \Rightarrow f(a,b) \in C[/math]
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].

Man kann diese Invariante noch um eine zweite Invariante ergänzen: [math]\forall a, b: a \not\in A \vee b\not\in B \Rightarrow f(a,b) {\rm\ throws\ error}[/math]
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.

Ob man die Datentypen einer Signatur eher als Vor- und Nachbedingung oder als Invariante 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.

3.2 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.

3.2.1 Anmerkung

In Sprachen wie Java, die keine Attribute unterstützen, werden öffentlich zugängliche Zustandsvariablen häufig als Attribute missbraucht.

3.3 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.

3.3.1 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.

3.4 Methodenbeschränkungen

Neben Methodensignaturen, Attributen und Zustandsvariablen können weitere Integritätsbedingungen im Klassenschema angegeben werden. Zum einen können für Methoden neben der Signatur weitere Invarianten sowie Vor- und Nachbedingungen angegeben 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
}

3.5 Zustandsbeschränkungen

Neben Integritätsbedingungen, die sich auf spezielle Methoden beziehen, kann man auch 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}

3.6 Beziehungsbeschränkungen

TBD

4 Quellen


Dieser Artikel ist GlossarWiki-konform.