Klassendiagramm

Einführung

Ein Klassendiagramm ist ein Strukturdiagramm der Unified Modeling Language (UML) zur grafischen Darstellung (Modellierung) von Klassen, deren Elementen und Beziehungen. Eine Klasse ist in der Objektorientierung ein abstrakter Oberbegriff für die Beschreibung der gemeinsamen Struktur und des gemeinsamen Verhaltens von Objekten.

Für die Sicht auf einen Programmentwurf sind Klassendiagramme einer der wichtigsten Diagrammtypen. Sie sind besonders interessant, da Hilfsprogramme aus diesen Diagrammen automatisch Teile des Quellcodes erzeugen können (http://openbook.galileocomputing.de/javainsel/javainsel_03_003.html#dodtpc835af77-af38-45e7-9ab8-d8b66a5f9d0f). Um Klassendiagramme zu modellieren, ist ein vorgegebene Syntax einzuhalten. Die Regeln zur Erstellung von Klassendiagramme werden im Folgenden erläutert.

Ein gute und verständliche Video-Vorlesung der TU-Wien findet sich hier: http://www.uml.ac.at/wp-content/uploads/teaching/02_Klassendiagramm_Video/02_Klassendiagramm.htm.

Klassen

Klassen werden durch Rechtecke dargestellt, die entweder nur den Namen der Klasse (fett gedruckt) tragen oder zusätzlich auch Attribute und Methoden spezifiziert haben. Dabei werden diese drei Rubriken – Klassenname, Attribute, Methoden– jeweils durch eine horizontale Linie getrennt. Wenn die Klasse keine Eigenschaften oder Methoden besitzt, kann die unterste horizontale Linie entfallen. 

Kann eine Klasse keine Instanzen erzeugen, bezeichnet man sie als abstrakt (abstract). Abstrakte Klassen werden durch einen kursiv geschriebenen Klassennamen oder durch den Zusatz {abstrakt} in geschweiften Klammern unterhalb des Klassennamens kenntlich gemacht.

(anklicken für größere Darstellung)

Attribute

Der Name eines Attributs muss eindeutig innerhalb einer Klasse sein. Nach dem Attribut folgt, falls bekannt, getrennt durch einen Doppelpunkt („:“) der zugehörige Datentyp. Statische (static) Attribute werden durch Unterstrich formatiert.

Friseursalon

Methoden

Die Eingabeparameter werden in runden Klammern () nach dem Methodennamen aufgeführt. Hier werden für jeden Eingabeparameter erst der Name des Parameters und dann der Datentyp genannt. Name und Datentyp werden durch einen Doppelpunkt : getrennt. Der Datentyp der Rückgabe erfolgt nach der Eingabeparameterliste und wird mit einem Doppelpunkt getrennt. Falls keine Rückgabe durch eine Methode erfolgen soll, kann auch auf die Angabe void verzichtet werden.

Ansonsten können bei Attributen und Methoden sämtliche primitiven Typen sowie selbst definierte Klassen oder Interfaces als Typ bzw. Rückgabetyp verwendet werden. Statische (static) Methoden werden zusätzlich durch einen Unterstrich hervorgehoben.

Sichtbarkeit

Die Sichtbarkeit (engl. visibility) von Konstruktoren, Datenfeldern und Methoden wird im UML-Klassendiagramm wie folgt gekennzeichnet:

Beziehungen

Klassen können untereinander in Beziehungen stehen. Die Beziehungen lassen sich in drei Kategorien einteilen: Die Generalisierung, die Abhängigkeit  und die Assoziation.

Generalsierung

Eine Generalisierung in der UML ist eine gerichtete Beziehung zwischen einer generelleren und einer spezielleren Klasse. Hier durch wird das Konzept der Vererbung dargestellt. Exemplare der spezielleren Klasse sind damit auch Exemplare der generelleren Klasse. Konkret bedeutet dies, dass die speziellere Klasse implizit über alle Merkmale (Struktur- und Verhaltensmerkmale) der generelleren Klasse verfügt – implizit deshalb, weil diese Merkmale in der spezielleren Klasse nicht explizit deklariert werden. Man sagt, dass die speziellere Klasse sie von der generelleren Klasse „erbt“ oder „ableitet“. Eine Generalisierung wird als durchgezogene Linie zwischen den beiden beteiligten Klassen dargestellt. Am Ende der Generalisierung wird eine geschlossene, nicht ausgefüllte Pfeilspitze gezeichnet. Geerbte Attribute und Methoden werden in der Darstellung der abgeleiteten Klasse nicht wiederholt.


Abhängigkeit

Die schwächste Form einer Beziehung ist die "Abhängigkeit" (engl. Dependency). 

Ein Objekt einer Klasse verwendet ein Objekt einer anderen Klasse im Code einer Methode. Wenn das Objekt in keinem Feld gespeichert ist, wird dies als Abhängigkeitsbeziehung modelliert. Beispielsweise kann die Person-Klasse eine hasRead-Methode mit einem Book-Parameter haben, der true zurückgibt, wenn die Person das Buch gelesen hat. Solche schwachen Abhängigkeiten können zum Beispiel durch statische Methodenaufrufe implementiert werden.

Abhängigkeiten werden durch eine gestrichelte Linie mit einer Pfeilspitze dargestellt.

Assoziation

Eine Assoziation beschreibt eine Beziehung zwischen zwei oder mehr Klassen. Die Beziehung legt dabei fest, wie Instanzen (Objekte) der im Klassendiagramm definierten Klassen zu einander in Verbindung stehen. Eine Assoziation wird durch einen einfachen Strich zwischen zwei Klassen dargestellt. An den Enden von Assoziationen sind häufig Multiplizitäten vermerkt. Diese drücken aus, wie viele dieser Objekte in Relation zu den anderen Objekten dieser Assoziation stehen können.

Im obigen Beispiel bedeutet dies, dass ein Team aus Null oder mehreren Personen bestehen kann. Jeder Person kann, muss aber nicht einem Team zugeordnet sein. Das Team und die Personen befinden sich bereits auf der Objektebene. Will man festlegen, dass ein Team nur aus 3-11 Spielern bestehen kann (z.B. eine Fußballmannschaft), gibt man dies durch Multiplizitäten an.

Will man zusätzlich festlegen, dass jede Person zu einem Team gehören MUSS, kann man die Angaben zur Multiplizität erweitern. Durch diese Assoziation hat man durch die Angaben zur Multiplizität festgelegt, dass eine Person einem Team angehören muss und jedes Team aus 3-11 Personen besteht.

Klassendiagramm mit Assoziation und Multiplizitäten

Gültiges Objektdiagramm

Das obige UML Objektdiagramm zeigt mögliche Objektbeziehungen, welche die Vorgaben des Klassendiagramms erfüllen. Jede Person ist einem Team zugeordnet. Das Team “brennball” besteht aus drei Personen (klaus, paula, martin).

Ungültiges Objektdiagramm

Dieses Objektdiagramm verletzt die Vorgaben des Klassendiagramms. Die Person “martin” ist nicht einem Team zugeordnet und das Team “brennball” besteht nur aus zwei Personen.

Komposition und Aggregation

Bisher haben wir die Beziehungen zwischen verschiedenen Objekten betrachtet. Doch ein Objekt selbst kann eine komplexe Struktur besitzen, und wir können die Beziehungen zwischen dem Objekt und seinen Teilen genauer festlegen. Dabei unterscheiden wir zwischen Komposition und Aggregation.

Sowohl die Komposition als auch die Aggregation sind Teil-von- bzw. Besteht-aus-Beziehungen. Wir können zum Beispiel modellieren, dass eine Bestellung aus den Bestellungspositionen oder dass eine Fußballmannschaft aus ihren Spielern besteht (http://openbook.galileocomputing.de/oo/oo_04_strukturvonooprogrammen_002.htm#Rxxob04strukturvonooprogrammen002040015221f03719e).

Aggregation

Von einer Aggregation sprechen wir, wenn ein Objekt ein Teil von mehreren zusammengesetzten Objekten sein kann. Die zusammengesetzten Objekte nennen wir in diesem Fall Aggregate. Die Lebensdauer der Teile kann dabei länger sein als die Lebensdauer der Aggregate.

Ein Beispiel für eine Aggregation ist die Beziehung zwischen einer Vorlesung und ihren Studenten. Ein Mensch kann mehrere Vorlesungen hören, und wird eine Vorlesung aufgelöst, bedeutet es in den allermeisten Fällen nicht das Ende für ihre Teilnehmer.

Die Aussage der Aggregation ist relativ dünn. Der Unterschied zur Assoziation kaum erkennbar. In der Praxis ist die Aggregation daher unbedeutend, da sie sich nicht auf eine Implementierung auswirkt.

Komposition

In der grafischen Darstellung einer Komposition dekoriert eine ausgefüllte Raute das Ende mit der Multiplizität 1, das mit dem Ganzen verbunden ist. Im Fall der Aggregation ist es eine nicht ausgefüllte Raute.

Das zusammengesetzte Objekt wird hier als Kompositum bezeichnet.

Ein Beispiel für eine Komposition ist die Beziehung zwischen einem Gebäude und den einzelnen Räumen des Hauses. Ein Raum gehört in genau ein Gebäude, und wird das Gebäude zerstört/gelöscht, löscht man automatisch auch alle ihre Räume. Folgende Unterschiede zur Aggregation existieren für Kompositionen:

Beispiele

Die Bedeutung für die Implementierung lässt sich am Besten anhand eines Beispiels nachvollziehen. Die Kompositionsbeziehung  zwischen Gebäude und Raum wird über eine dynamische Datenstruktur (hier ein ArrayList) und den Konstruktor der Klasse Gebaeude realisiert.

public class Raum{....}// Klasse Raum mit beliebigen Attributen und Methoden

public class Gebaeude {

    private ArrayList <Raum> raumListe = new ArrayList <Raum>();// Die ArrayList speichert beliebig viele Räume

    public Gebaeude (int AnzahlRaeume){ // Durch den Konstruktor werden die Einzelteile erzeugt

        // Die For-Schleife legt die Räume an

        for(int i=0; i<AnzahlRaeume;i++){

            raumListe.add(new Raum());// Erzeugt die Instanzen der Räume des Gebäudes

        }

    }

}

Navigierbarkeit

Eine weitere Eigenschaft einer Beziehung ist ihre Navigierbarkeit. Man spricht in diesem Zusammenhang auch von einer "gerichteten" Beziehung. Die Navigierbarkeit sagt etwas darüber aus, ob die eine Klasse auf die in beziehungstehende Klasse zugreifen kann bzw. Objekte dieser Klasse kennt. Die Navigierbarkeit wird meistens in den Designmodellen angegeben, in den Analysemodellen spielt die Navigierbarkeit selten eine Rolle.

In UML wird eine Beziehung, die nur in einer Richtung navigierbar ist, durch einen Pfeil an dem Ende, zu dem wir navigieren können, angezeigt. Ist eine Beziehung in beide Richtungen navigierbar, wird dies  durch einen Doppelpfeil angezeigt.  (http://openbook.galileocomputing.de/oo/oo_04_strukturvonooprogrammen_002.htm#Rxxob04strukturvonooprogrammen002040015221f037152).

Werden keine Pfeile angegeben, wird keine Aussage zur Navigierbarkeit getroffen. Man kann somit den Zugriff auf die Objekte einer Beziehung realisieren, man muss es aber nicht. Soll der Zugriff explizit ausgeschlossen werden, wird anstatt eines Pfeils ein Kreuz modelliert.

Beispiel

Die Navigierbarkeit wird nun anhand eines Beispiels verdeutlicht. Zwischen den Kunden eines Friseursalons und ihren Terminen besteht eine Assoziation. Ein Kunde kann beliebig viele Termine haben und ein Termin muss einem Kunden zugewiesen werden. Die Klassen Kunde und Termine werden als Kästen dargestellt und mit entsprechenden Multiplizitäten versehen (siehe Abbildung).

Ohne Navigierbarkeit

Werden keine Angaben zur Navigierbarkeit gemacht, würde folgende Implementierung ausreichen, um das Design in einem Java-Programm abzubilden:

public class Kunde {

}

public class Termin {

    private Kunde kunde;

    public Termin (Kunde kunde){

        this.kunde = kunde;

    }

}

Die Klasse Termin hat ein Attribut kunde vom komplexen Datentyp Kunde. In diesem Attribut wird mittels eines Zeigers auf ein Objekt der Klasse Kunde verwiesen. Ohne Navigierbarkeit wird nicht gefordert, dass ein Kunde seine Termine kennen muss. Es besteht die Möglichkeit, dass mehrere Termine auf ein identisches Kundenobjekt zeigen. Ein Kunde hat mehrere Termine. Jeder Termin muss aufgrund des Konstruktors einer Kundeninstanz zugewiesen werden. Das Design ohne Navigierbarkeit ist erfüllt.


Unidirektional

Zeigt der Pfeil von Termin nach Kunde, ist dieses Design durch die oben aufgeführte Implementierung ebenfalls bereits realisiert. Ein Termin kennt seinen Kunden, in dem man dem Zeiger auf die Kundeninstanz folgen  und dieses Objekt aufgerufen werden kann. Außerdem wurde für diesen Fall noch ein Kreuz ans andere Ende der Beziehung modelliert, um sicherzustellen, dass kein Kunde seine Termine aufrufen kann.

Zeigt der Pfeil von Kunde auf Termin reicht die oben aufgeführte Implementierung nicht aus. Es gibt keine Möglichkeit für eine Instanz der Klasse Kunde, sich  seine zugewiesenen Termine anzeigen zu lassen oder diese aufzurufen. Termine sind dem Kunden unbekannt.

Da ein Kunde beliebig viele Termine haben kann, benötigen wir ein Hilfsobjekt, dass es ermöglicht, eine beliebige Anzahl von Referenzen zu verwalten. Es gibt eine Vielzahl an dynamischen Datenstrukturen um die Beziehungen zwischen Kunde und Termin zu verwalten. Exemplarisch wird für die folgende Implementierung die ArrayList eingesetzt.

public class Kunde {

private ArrayList<Termin> terminListe = new ArrayList<Termin>();

public void fuegeTerminHinzu(Termin t){

this.terminListe.add(t);

}

}

Durch die Methode fuegeTerminHinzu kann ein Objekt der Klasse Termin der terminListe hinzugefügt werden. Die terminListe verweist auf ein Objekt der Klasse ArrayList. Hier können beliebig viele Zeiger auf Termin Objekte verwaltet werden. Somit ist der Zugriff auf Termin eines Kunden möglich.

Bidirektional

Die Bidirektionalität ergibt sich durch die Kombination der  beiden unidirektionalen Varianten. Die Pfeile zeigen von Termin auf Kunde und von Kunde auf Termin. Folgende Implementierung realisiert das bidirektionale Design:

public class Kunde {

private ArrayList<Termin> terminListe = new ArrayList<Termin>();

public void fuegeTerminHinzu(Termin t){

this.terminListe.add(t);

}

}

public class Termin {

    private Kunde kunde;

    public Termin (Kunde kunde){

        this.kunde = kunde;

    }

}

static

Statische Attribute und Methoden werden in UML mittels Unterstrich dargestellt. So sind im unteren Schaubild die Instanzvariablen count und die Methode get_count() statisch.