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:
„+“ für public – (engl. öffentlich), unbeschränkter Zugriff
„#“ für protected – (engl. geschützt), Zugriff von Klassen des Packages (in BlueJ Projekt) sowie von Unterklassen (Klassen, die erben)
„−“ für private – (engl. privat), nur die Klasse selbst kann es sehen
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:
Die Kardinalität auf der Kompositumseite kann nur 1 sein.
Jedes Teil ist von nur genau einem Kompositionsobjekt abhängig.
Die Lebenszeit der Einzelteile ist der des Ganzen untergeordnet. Sie werden also zusammen mit dem Aggregat erzeugt, und sie werden zusammen mit dem Aggregat zerstört (Analyse und Design mit UML 2.3: objektorientierte Softwareentwicklung von Bernd Oestereich, Oldenbourg 2009 Seite 94 ff)
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).
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.