Kapitel 4 Modellierung und UML
Der objektorientierten Programmierung und ihren Vererbungsmechanismen liegen allgemeine Klassifikations- und Modellierungs-Prinzipien zugrunde.
Neben den Grundlagen werden anhand von Beispielen die wesentlichen Elemente der Klassen-, Objekt- und Sequenz-Diagramme der Unified Modeling Language (UML) vorgestellt.
Gerade für das Design und die Interaktion von Java-Klassen sind UML-Diagramme recht gut geeignet und werden deshalb in den folgenden Kapiteln bei komplizierteren Beispielen benutzt.
Design-Prinzip: wiederverwendbare Komponenten, Module, Systeme
Bisher konnten die Wirkungsweisen von Operatoren und vielen Anweisungen recht gut an einzelnen Methoden und Code-Fragmenten demonstriert werden. Klassen – wenn überhaupt – wurden bisher als isolierte Einheiten betrachtet.
Betrachtet man die Ingenieurwissenschaften allgemein, so sind diese geprägt vom Design wiederverwendbarer Komponenten, integriert zu Modulen und Systemen, die letztendlich dann Produkte ergeben.
In Analogie sind die Klassen im Software-Engineering die elementaren Komponenten, die im Verbund ein Produkt ergeben sollen.
Soweit zur Theorie der »Brave new world«.
Normen
Damit aus einer Fiktion Realität wird, müssen einheitliche Normen und Standards – über Programmiersprachen, Betriebssysteme und Hardware hinweg – definiert und akzeptiert werden.
Aber nicht nur das. Die Ausbildung, weg vom Programmierer als »Codierer« hin zum Software-Ingenieur, muss sich ebenfalls an dieser Idee orientieren. Dagegen steht aber häufig eine Programmiermentalität, die das Rad immer wieder neu erfinden will.1
Ein erster wichtiger Schritt sind Modellierungen von Design-Ideen bzw.
-Entscheidungen, die neben einem implementationsunabhängigen Überblick auch die Basis für Design-Techniken und Idiome bilden. Es folgt deshalb ein kurzer Überblick auf Basis der UML.
4.1 Generalisierung und Spezialisierung ![down](common/down.gif) ![top](common/15x15leer.gif)
Objektorientiertes Paradigma
Die Vererbung war Mitte der Achzigerjahre der Ausgangspunkt für die Ablösung von C durch C++ und letztlich die Ablösung des strukturierten durch das objektorientierte Paradigma.
Ein Paradigma ist ein Denkmuster, eine Sicht oder ein Blickwinkel auf die Umwelt. Ein vertrautes Denkmuster wirft man nicht so einfach weg, d.h., eine Ablösung geschieht nicht abrupt, sondern graduell.
Wird aber umgekehrt ein Paradigma wie das objektorientierte dann en vogue, so meint man, mit ihm plötzlich alle Probleme der betrachteten Umwelt einfach und elegant lösen zu können.
Einige Zeit und kritische Geister werden benötigt, dies dann zu widerlegen. Der Prozess ist allerdings zyklisch.
4.1.1 Klassifikation: Denken in Hierarchien ![down](common/down.gif) ![top](common/top.gif)
Klassifikation
Ausgangspunkt der Objekt-Orientierung ist die Klassifikation. Für Biologen ist dies die natürliche Arbeitsweise. Generelles wird vom Speziellen separiert, um dies, wenn möglich, in Kategorien hierarchisch zu organisieren.
Hat man generelle Merkmale und Strukturen erkannt, kann man spezielle daraus ableiten. Es entsteht eine Hierarchie, die, grafisch betrachtet, häufig einem (umgekehrten) Baum mit einer Wurzel (oberstes Element) entspricht.
Abbildung 4.1 Klassifikation, d.h. Ableiten vom Generellen zum Speziellen
Diskriminator
Bei der Klassifikation ist der Diskriminator entscheidend. Es ist das Unterscheidungsmerkmal, anhand dessen man die Hierarchie überhaupt bildet.
|
Ein guter Diskriminator ist erstens nicht einfach zu finden und zweitens selten eindeutig. |
Diskriminatoren sind nicht eindeutig
Man kann sich also über Diskriminatoren trefflich streiten. Häufig sind sie auch nicht so richtig klar bzw. ändern ihre Bedeutung von (Hierarchie-) Stufe zu Stufe.
Als Lernobjekte bzw. Beispiele sind besonders die Hierarchien interessant, bei denen der Diskriminator innerhalb der Stufe oder von einer Stufe zur anderen wechselt, ohne explizit angegeben zu werden.
Ein sehr beliebtes Beispiel ist die Generalisierung bzw. Spezialisierung von Fahrzeugen (siehe Abb. 4.2).
Wechselnde
Diskriminatoren
Abbildung 4.2 Fahrzeug-Hierarchie mit wechselnden Diskriminatoren
Die Diskriminatoren Di der drei Spezialisierungsstufen der Fahrzeug-Hierarchie sind dann:
|
D1: Primärer Transport von Gütern vs. Personen (wobei bei Personentransport nach Anzahl der Räder unterschieden wird) |
|
D2: Karosserieform in Kombination mit Personen-/Sitzanzahl |
|
D3: Hersteller |
Das in Abb. 4.2 dargestellte Beispiel kann natürlich durch Einfügen von Fahrzeugvarianten wie Van, Pickup, Chopper oder weiterer Diskriminatoren wie Schienenbindung, Antriebsart (Benziner, Diesel etc.) beliebig kompliziert gestaltet werden.
Baum vs.
azyklischer Graph
Bei konkurrierenden Diskriminatoren entartet ein Baum dann häufig in einen azyklischen Graphen (ohne Kreise), was sofort Auswirkungen auf die Programmiersprache hat.2
![Icon](common/D.gif)
Doch egal welchen Diskriminator man wählt, allen Spezialisierungen sollte die folgende Beziehungsart gemeinsam sein.
Design-Prinzip:
Is-A
Is-A-Beziehung: Ein Objekt der spezielleren Kategorie (Klasse) ist aufgrund des Diskriminators ein besonderer Repräsentant der generelleren Kategorie (Klasse).
|
extends:
Is-A in Java
In Java wird eine Spezialisierung durch das Schlüsselwort extends ausgedrückt, genauer:
class SpecialClass extends GeneralClass {...}
class PKW extends Landfahrzeug {...}
class Coupe extends PKW {...}
...
![Icon](common/R.gif)
Aus der Is-A-Beziehung leitet sich das für objektorientierte Sprachen äußerst wichtige »Stellvertreter«-Prinzip ab.
Substitutions-Regel
Substitutions-Regel: Ein Objekt der spezielleren Kategorie (Klasse) kann jederzeit anstelle eines Objekts der generelleren Kategorie (Klasse) stehen (wobei die Umkehrung nicht gilt!).
|
Is-A und Substitution findet man in alltäglichen Situationen:
Bucht man einen Mietwagen einer Kategorie, so steht es dem Verleihunternehmen frei, wie es diese Buchung konkret spezialisiert. Bucht man umgekehrt ein Fahrzeug eines speziellen Herstellers (Porsche Cabrio), so ist man über eine Limousine der gleichen Kategorie eines anderen Herstellers nicht sonderlich erfreut (Verletzung des Substitutions-Prinzips!).
Werden die Kosten für einen Mietwagen (PKW) z.B. in Java durch die Methode
static double gesamtKosten(PKW
mw,int anzTage) {
//...
}
berechnet, so kann die Methode für jeden speziellen PKW aufgerufen werden:
kostenCoupe= gesamtKosten(coupe,4); // Coupe-Objekt
Abstraktionsgrad
Im Zusammenhang mit der Generalisierung ist der Grad der Abstraktion von Bedeutung, den jede Stufe in der Hierarchie einfügt.
![Icon](common/Kaktus.gif)
Abbildung 4.3 »Merkwürdige« Spezialisierung von Abteilung
Spezialisierung:
Was ist eine signifikante Änderung?
Spätestens bei der letzten Spezialisierung in die diversen Werkstätten merkt man, dass an dieser Hierarchie etwas nicht in Ordnung ist. Untersucht man den zugehörigen Diskriminator, so heißt er schlicht »Werkstattnummer«.
Des Weiteren stellt man fest, dass mit der genannten Spezialisierung nicht Kategorien bzw. Klassen von Objekten beschrieben werden, sondern nur einzelne Objekte.
class Fertigung {}
class Werkstatt1 extends Fertigung {}
class Werkstatt2 extends Fertigung {}
//...
In Java könnte sinnvoll nur ein Objekt pro Klasse angelegt werden:
Werkstatt1 w1= new Werkstatt1();
Werkstatt2 w2= new Werkstatt2();
//...
Auch hier gibt es eine Orientierungshilfe.
![Icon](common/D.gif)
Klassen
Klassifikations-Prinzip: Eine Klassifikation bzw. Spezialisierung führt potenziell3 zu Klassen, wobei diese eine Menge von Objekten mit gleichem Verhalten, aber unterschiedlichen Eigenschaftswerten enthalten.4
|
Dieses Prinzip hat ein positives bzw. negatives Korollar:3 4 5
Spezialisierung: Neue(s)
Verhalten/ Eigenschaften
|
Positiv: Eine neue Spezialisierung verändert das Verhalten bzw. führt neue Eigenschaften ein. |
|
Negativ: Verändert eine neue Spezialisierung nur die Werte von bestehenden Eigenschaften, so hat man keine neue Spezialierung, sondern nur eine Variation in den Werten gefunden (spezielle Objekte). |
Aufgrund des (negativen) Korollars sind zumindest die Werkstätten in Abb. 4.3 in eine Klasse Werkstatt zusammenzufassen.
class Werkstatt {}
Von dieser Klasse können Werkstätten als Objekte erschaffen werden:
Werkstatt[] werkstatt= new Werkstatt[10];
Betrachtet man das Diagramm in Abb. 4.3 als eine Art Überblick über die Abteilungsstruktur – sprich Organigramm –, so ist die gesamte Spezialisierung hinfällig.
Der Diskriminator ist dann »Abteilungsname«. Man hat wieder eine Variation in der Eigenschaft name, und die Lösung ist dann schlicht und ergreifend eine einzige Klasse Abteilung (siehe auch Abb. 4.10):
class Abteilung {
String name; // "Verkauf", "Einkauf", "Werkstatt5"
void setName (String bez) { name= bez; }
//...
}
Nun sind alle in Abb. 4.3 dargestellten Spezialisierungen Objekte:
Abteilung verkauf= new Abteilung();
Abteilung werkstatt5= new Abteilung();
![Icon](common/D.gif)
Die vorgestellten Regeln und Prinzipien haben weit reichende Bedeutung für die Java-Klassenbildung:
Spezielle Klassen »erben« Methoden und Eigenschaften
|
Das Verhalten von Klassen-Objekten wird durch die Methoden der Klasse festgelegt, Objekt-Eigenschaften – auch Attribute genannt –durch die Felder der Objekte. |
Verhalten = Methode,
Eigenschaften = Felder
|
Die Substitutions-Regel impliziert, dass alle öffentlich zugänglichen Methoden und Eigenschaften der generellen Klasse bei der speziellen auch vorhanden sein müssen. |
Spezialisierung:
Neue bzw. veränderte Methoden bzw. Felder
|
Nach dem Klassifikations-Prinzip ist eine spezialisierte Klasse nur dann notwendig, wenn die öffentlich zugänglichen Methoden und Eigenschaften der generellen Klasse modifiziert werden müssen. |
4.1.2 Dynamische Polymorphie ![down](common/down.gif) ![top](common/top.gif)
Der letzte Punkt führt scheinbar zu einem Dilemma. Denn nach der Substitutions-Regel muss die spezialisierte Klasse alle Methoden der generellen übernehmen, andererseits soll sie ihr Verhalten aber signifikant verändern.
Polymorphie – die Vielgestaltigkeit – angewendet auf Methoden bedeutet, dass die spezielle Klasse zwar die Methoden der generellen Klasse übernehmen muss, aber sie passend zum veränderten Verhalten neu implementieren darf.
![Icon](common/R.gif)
Bei Substitution: Aufruf der passenden Methode
Damit wird die Substitutions-Regel nicht nur gewahrt, sondern sogar auf die Methoden ausgedehnt:
|
Von einem speziellen Objekt, das ein generelles substituiert (z.B. als Argument beim Methodenaufruf), wird automatisch die zugehörige Methode aufgerufen. |
Hat z.B. die Coupe-Klasse einen 10-prozentigen Aufschlag, so erwartet man beim Aufruf der Methode gesamtKosten(coupe,4), dass die zum Coupe-Objekt passende Methode getTarif() aufgerufen wird:
class Kategorie { double tarif; }
class PKW {
Kategorie kat;
public double getTarif() { return kat.tarif; }
}
class Coupe extends PKW {
public double getTarif() { return kat.tarif * 1.1; }
}
4.2 Klassen-Beziehungen in der UML ![down](common/down.gif) ![top](common/top.gif)
Abbildungen und Diagramme haben den Vorteil, durch eine grafische Darstellung Zusammenhänge zu verdeutlichen, die verbal nur mühsam zu formulieren sind.
UML: Ein
grafischer Werkzeugkasten
Wenn die Art, Bau- und Konstruktionspläne zu zeichnen, vom jeweiligen Architekten bzw. Ingenieur abhängen würde, wäre diese Welt um viele skurrile Gebäude, Maschinen und Abenteuer der Technik reicher.
Die UML – Unified Modeling Language – ist im Software-Engineering ein De-facto-Standard für grafisch orientiertes Design. Sie gleicht einer Werkzeugkiste, die auf Programmiersprachen anpassbare Werkzeuge für die verschiedenen Belange der Design-Phasen enthält.
Wie bei Bauzeichnungen gibt es zwar feststehende Symbole, Normen und Regeln, aber dies bedeutet noch keine logische Vorgehensweise bzw. kein grandioses Design.
![Icon](common/Kaktus.gif)
Frei nach »a fool with a tool is still a fool« können UML-Diagramme auch Schwachsinn korrekt dokumentieren.
|
Diese Einführung ist sehr kurz und beschränkt sich auf die Werkzeuge, die hauptsächlich benötigt werden, d.h. Klassen-, Objekt- und Sequenz-Diagramme.
Alle anderen Diagrammtypen – sofern verwendet – werden hier nicht vorgestellt, sie werden im Kontext erklärt oder sind intuitiv verständlich (siehe z.B. Abb. 3.1).
4.2.1 Klasse ![down](common/down.gif) ![top](common/top.gif)
Die einfachste Art, in der UML eine Klasse darzustellen, ist ein Klassensymbol: ein Rechteck, welches den Klassennamen enthält.
Einfache Darstellung von Klassen
Abbildung 4.4 Einfache Darstellung von Klassen in der UML
Sollen Interna der Klasse dargestellt werden, kann das Rechteck je nach Bedarf in zwei oder drei Fächer (compartments) eingeteilt werden (siehe Abb. 4.5).
Klassen mit Fächern (Compartments)
Abbildung 4.5 Klassen-Darstellung mit drei Fächern (compartments)
In der Klassen-Notation treten noch folgende Begriffe auf, deren Semantik auf die Programmiersprache abzubilden ist:
Sichtbarkeit
(Visibility)
|
Unter Sichtbarkeit versteht man die Zugriffsrestriktionen: |
|
|
+ für public: alle haben Zugriff, |
|
|
# für protected: die Klasse und alle ihre Spezialisierungen haben Zugriff und |
|
|
- für private: nur die Klasse hat Zugriff. |
Klassen-Attribute und -Operationen
|
Klassen-Attribute bzw. -Operationen werden durch Unterstreichen gekennzeichnet. |
Da Klassen-Attribute nicht zum Objekt, sondern zur Klasse gehören, werden sie von allen Objekten benutzt und enthalten daher objektunabhängige Informationen (siehe rechtes Beispiel in Abb. 4.5).
Diese ausführliche Klassen-Darstellung ist nahezu beliebig variierbar.
Variationen in der Darstellung von Klassen
Sehr häufig werden nur die Operationen dargestellt, die außerhalb der Klasse verwendet werden können (zwei Fächer, ohne Sichtbarkeitsangabe). Bei Operationen werden häufig die Parameternamen und/oder Typenangaben weggelassen.
Wird ausschließlich Java als Implementierungssprache verwendet, werden sogar Attribute und/oder Operationen als Java-Deklarationen geschrieben (was natürlich nicht der UML-Notation entspricht, aber durchaus praktisch sein kann).
4.2.2 Generalisierung und Spezialisierung ![down](common/down.gif) ![top](common/top.gif)
Generalisierung und Spezialisierung werden in Form von Oberklassen (Superklassen) und Unterklassen (Subklassen) modelliert.
Eine Unterklasse wird durch einen geschlossenen Pfeil aus der Oberklasse abgeleitet, wobei der Pfeil in Richtung Oberklasse zeigt.
Superklasse – Subklasse
Abbildung 4.6 Superklasse «Person», Subklasse «Mitarbeiter»
4.2.3 Klassifikation von Beziehungen ![down](common/down.gif) ![top](common/top.gif)
Auch wenn es dem OO-Paradigma zuwiderläuft, nicht sehr viele Beziehungen zwischen Klassen bzw. Objekten lassen sich in das Generalisierungs-/Spezialisierungs-Schema pressen. Versucht man es trotzdem, rächt sich dies spätestens bei der Implementierung, d.h. bei der Erstellung der Applikation.
In Abb. 4.7 sind neben der Spezialisierung weitere wichtige Beziehungen dargestellt.
Arten von
Beziehungen (nicht in der UML)
Abbildung 4.7 Klassifikation von Beziehungen
Für die Unterscheidung zwischen Ähnlichkeit und Spezialisierung gibt es keine einfachen Kriterien.
![Icon](common/T.gif)
Im Zweifelsfall halte man sich an die einfache Heuristik: »Es liegt Ähnlichkeit vor«.
|
Die UML – bereits abgestimmt auf Java – differenziert ein wenig anders als oben dargestellt.
Wichtige UML-Beziehungen für Java
Abbildung 4.8 Wichtige UML-Beziehungen für Java
Die Assoziation umfasst Zuordnung/Mitglied, Aggregation und Komposition differenzieren die Teil-von-Beziehung, und die Implementation (Realization) deckt das große Feld der Ähnlichkeit ab.6
Anhand von Java-Klassen-Fragmenten sollen die einzelnen Beziehungen kurz vorgestellt werden.
4.2.4 Assoziation ![down](common/down.gif) ![top](common/top.gif)
Bei der Assoziation beschränken wir uns hier auf den häufigsten Fall der binären Assoziation. Assoziationen beschreiben Beziehungen wie:
Assoziation:
isRelatedTo, isMemberOf
Student besucht Vorlesung (isRelatedTo – eine Beziehung), Student gehört zur Praktikumsgruppe (isMemberOf – eine Gruppen-Beziehung).
Abbildung 4.9 Assoziation (isMemberOf) mit Multiplizität7
Beziehungen vs. Relationen
Beziehungen sind mathematisch gesehen Relationen mit entsprechender Semantik. Diese Sicht ist für das Verständnis der Multiplizitäten interessant, die sich auf die Objekte beziehen (siehe Regeln!).
Mathematische Relationen
Sei Student = { s1, ... ,sn } und Praktikum = { p1, ... ,pn }, dann ist die Assoziation mathematisch eine Relation:
isMemberOf = { (s1,p1), (s2,p1), (s2,p2), (s2,p3), (s3,p1), (s3,p3), (s5,p1), ... }
![Icon](common/R.gif)
Für Assoziationen gelten folgende Regeln:
Abhängigkeiten,
Multiplizität, Symbol *
1. |
Zwischen den beteiligten Klassen bestehen keine essenziellen Abhängigkeiten, d.h., ihre Objekte können unabhängig voneinander erschaffen und zerstört werden. |
2. |
Die Multiplizität min..max an der Beziehung beschreibt die minimale bzw. maximale Anzahl von Objekten, mit der ein Objekt der gegenüberliegenden Klasse verbunden ist. Ist min gleich max, genügt eine Angabe. |
3. |
Der Stern * steht für ein beliebiges oder nicht näher spezifiziertes Maximum. Steht nur ein Stern, ist dies die Abkürzung für 0..*. |
Erklärung zu Abb. 4.9: Zwischen Student und Praktika bestehen keine essenziellen Abhängigkeiten (ein Student wird nicht entsorgt, wenn das Praktikum aufgelöst wird!).
Die angegebene Multiplizität 4..18 steht für die Forderung, dass jedes Praktikum aus zumindest vier bis maximal 18 Studenten bestehen sollte. Ein Student kann an einer nicht näher spezifizierten Anzahl von Praktika teilnehmen.
isMemberOf
Die isMemberOf-Beziehung soll den besonderen Set-Charakter der Assoziation hervorheben und hat die Bezeichnung member.
Referenzen: Assoziationen in Java
In Java werden (binäre) Assoziationen am einfachsten durch Referenzen der beteiligten Klassen aufeinander realisiert.
class Student { ... }
class Praktikum { // nur relevanter Teil
final int MAXSTUDS= 18;
int numStud;
Student[] pStudent; // hält isMemberOf-Beziehung fest
boolean joinStudent (Student stud) { // z.B.
triviale
if (numStud==MAXSTUDS) return false; // Implementation
if (pStudent==null) pStudent= new Student[MAXSTUDS];
pStudent[numStud++]= stud;
return true;
}
}
Rollen
Ist die Rolle unklar, die ein Objekt bei der Beziehung spielt, kann sie durch Beziehungsnamen konkretisiert werden.
Dies ist immer bei rekursiven Beziehungen (der Klasse mit sich selbst) notwendig (siehe Abb. 4.10).
Abbildung 4.10 Organigramm – rekursiver Aufbau einer Hierarchie
Abb. 4.10 zeigt eine Lösung zu dem Organigramm in Abb. 4.3, welche nicht nur keine Spezialisierung verwendet, sondern den entscheidenden Vorteil hat, dass die Hierarchie nicht begrenzt ist.
Ein Einmannunternehmen kann somit genauso gut dargestellt werden wie ein Konzern.8
Rekursive Klassen-Struktur in Java
Die zugehörige Java-Lösung ist natürlich auch rekursiv und damit verblüffend einfach:
class Abteilung { // nur relevanter Teil
Abteilung oAbt; // Referenz auf Objekt derselben Klasse,
// d.h. die übergeordnete Abteilung
void setOberAbteilung (Abteilung oAbt) {
this.oAbt= oAbt; // zu this siehe 3.5.3
}
}
Rekursive Strukturen implizieren rekursive Methoden
Die Referenz oAbt zeigt immer auf die übergeordnete Abteilung, wobei nur die »Geschäftsführung« keine mehr hat, d.h., oAbt ist dann null.
Rekursive Klassen-Strukturen ziehen zur Traversierung ihrer Struktur rekursive Algorithmen, d.h. Methoden, nach sich.
Navigation
Assoziationen zeigen die logischen Zusammenhänge zwischen Klassen. Interessant an der Implementation ist aber, ob man von jeder Seite der Beziehung oder nur von einer Seite zur anderen »navigieren« kann.
Gerichtete Pfeile signalisieren
Navigierbarkeit
|
Ist aufgrund der Implementation nur in eine Richtung ein Zugriff möglich, sollte die Linie durch einen gerichteten Pfeil in diese Richtung ersetzt werden (siehe Abb. 4.11). |
Ein Linie ohne Pfeile bedeutet in den weitaus meisten Fällen nicht Navigation in beide Richtungen, sondern nur, dass sie nicht näher spezifiziert ist. Man lässt die Implementation einfach offen.
|
Soll ein Diagramm die Implementation widerspiegeln, so sollten Linien durch Doppelpfeile ersetzt werden, sofern in beide Richtungen navigiert werden kann (siehe Abb. 4.11). |
Abbildung 4.11 Navigierbarkeit in der Implementation
4.2.5 Aggregation und Komposition ![down](common/down.gif) ![top](common/top.gif)
Aggregation:
Eine istTeilvon-Beziehung
Ist die Assoziation eine Ganzes-Teile-Beziehung (whole-part-relation), in der ein Objekt der einen Klasse logisch gesehen aus Teilen der anderen Klasse besteht, modelliert man dies als Aggregation oder sogar Komposition.
Komposition: Eine existenzabhängige Aggregation
Der Unterschied zwischen Aggregation und Komposition liegt darin, dass die enthaltenen Objekte existenziell abhängig sind vom Aggregations-Objekt, d.h., wird das Aggregations-Objekt zerstört, sind auch die enthaltenen Objekte nicht mehr existent.
|
Bei einer Aggregation wird die Verbindungslinie auf der Aggregat-Seite durch eine Raute ergänzt, bei der Komposition ist das Innere der Raute schwarz. |
Die Beziehungen im folgenden Diagramm sind an das Collection Framework angelehnt, wobei Map und Entry als Klasse modelliert sind. In der J2SE sind sie Interfaces.
Abbildung 4.12 Map mit Einträgen, Schlüssel und Werten
Map: Container für Schlüssel-Werte-Paare
Unter einer Map versteht man in Java einen Container (Behälter) – in Java generell auch Kollektion genannt –, der Werte (Values) zusammen mit ihren Schlüsseln (Keys) aufnehmen kann. Ein Schlüssel-Werte-Paar heißt dann Entry bzw. Eintrag (siehe Abb. 4.12).9
Der Schlüssel muss eindeutig sein, darf also nur einmal in der Map bzw. in allen Einträgen vorkommen (Multiplizität 1–1). Anhand seines Schlüssels kann somit ein Wert-Objekt eindeutig in der Map gefunden werden.
Ein Eintrag gehört nur zu einer Map. Wird die Map zerstört, sind alle Einträge ebenfalls entsorgt.
Die Schlüssel- und Werte-Objekte sind unabhängig von der Map. Sie können durchaus in anderen Maps oder Objekten enthalten sein. Ihre Existenz hängt damit nicht von dem Eintrag oder der Map ab.
Multiplizität 0..1 bei Kompositionen
Objekte können nur in einem Kompositions-Objekt enthalten sein, die Multiplizität ist damit auf der Kompositions-Seite 0..1 oder 1 und wird in der Regel weggelassen.
Für Kompositionen lässt die UML auch eine alternative Darstellung zu (siehe rechtes Diagramm in Abb. 4.13).
![Icon](common/R.gif)
Primitive Typen werden nicht modelliert
Felder mit primitivem Typ haben immer Werte-Semantik und sind damit immer existenziell vom Objekt abhängig. Hier gilt die Regel:
|
Primitive Attribute eines Objekts sind nicht als Komposition zu modellieren (Beziehungen bestehen nur zwischen Klassen!). |
Im folgenden Diagramm (Abb. 4.13) wird ein Top-Level-Container Frame aus dem Package java.awt dargestellt, welches aus einer Titel-, einer Menü-Leiste und vielen Komponenten besteht.10
Abbildung 4.13 Alternative für Kompositionen am Beispiel Frame
Aggregation und Komposition werden in Java ebenfalls durch Referenzen in den Klassen dargestellt, da sie ja nur spezielle Formen der Assoziation sind (siehe Abb. 4.8).
Man könnte damit zur Tagesordnung übergehen, wenn da nicht die essenzielle Abhängigkeit bei der Komposition und die Forderung bestehen würde, dass die Teil-Objekte nur in einem einzigen Kompositions-Objekt sein dürfen.
Es gibt keine reine Objekt-Komposition in Java
|
Für Komposition gibt es – mit Ausnahme der Felder vom primitiven Typ – in Java kein direktes sprachliches Äquivalent/Konstrukt. |
Das Problem liegt in der Referenz-Semantik von Objekten.
Sobald das Kompositions-Objekt eine Referenz des enthaltenen Objekts nach außen liefert, können beliebig viele Objekte das Teil-Objekt referenzieren. Damit kann es gar nicht mehr essenziell abhängig sein und jederzeit in beliebigen Assoziationen (auch Kompositionen) auftreten.
Komposition durch Programm-Design
|
Die Kompositions-Problematik ist also nicht trivial und taucht leider in vielen Bereichen auf. |
![Icon](common/D.gif)
Komposition muss durch spezielle Methoden, Konventionen oder (im Spezialfall) durch innere Klassen sichergestellt werden.11
Ein passendes Design-Konzept zur Komposition ist ein
Komposition durch exklusiven Zugriff
exklusiver Zugriff: Das äußere Objekt erschafft das innere und liefert keine Referenz des inneren Objekts nach außen, sondern – sofern notwendig – nur eine Kopie, d.h. einen Klon.
|
Klon (Clone):
Eine Kopie des Objekts
Ein Klon bzw. Clone ist ein neues Objekt derselben Klasse, wobei die Felder dieselben Werte haben wie die des Original-Objekts.
Beispiel
Komposition in Java am Beispiel Auftrag
Das o.a. Design-Konzept wird anhand des Auftrags in Abb. 4.14 mit minimalistischen (Java-)Mitteln (ohne Cloning, Konstruktoren, Ausnahmebehandlung und Zugriffsschutz) umgesetzt.
Abbildung 4.14 Auftrag mit einzelnen Positionen
class AuftragsPosition { // Klasse als Datenstruktur
String artikel; // wie ein struct in C++
double preis;
}
class Auftrag { // nur relevanter Code
private List apList= new ArrayList(); // Positionen
void addAuftragPos (String artikel, double preis)
{
AuftragsPosition ap= new AuftragsPosition(); ¨
ap.artikel= artikel; ap.preis= preis;
apList.add(ap); // am Listen-Ende einfügen
}
AuftragsPosition getAuftragsPosition(int pos)
{
if (pos < apList.size()) {
AuftragsPosition ap= (AuftragsPosition)apList.get(pos);
AuftragsPosition clone= new AuftragsPosition(); ¦
clone.artikel= ap.artikel; clone.preis= ap.preis;
return clone; // nicht: return ap; ¬
} else return null;
}
}
Erklärung: Bei ¨ wird eine Auftragsposition als inneres Objekt erschaffen, zu dem äußere Objekte keinen Zugriff haben. Mit ¦ und ¬ wird eine Kopie herausgereicht.
Wird alternativ bei ¬ die Auftragsposition mit return ap; zurückgeliefert, kann mit Hilfe der Referenz ap das Auftragspositions-Objekt auch außerhalb des Auftrags verwendet werden. Damit wäre dies dann keine Komposition mehr, sondern eine Aggregation.
4.2.6 Nützliche UML-Erweiterungen ![down](common/down.gif) ![top](common/top.gif)
Da die UML sprachunabhängig ist, muss es möglich sein, durch Erweiterungen (oder Auslassungen) UML-Diagramme an die spezifischen Belange einer Sprache anzupassen.
Zusicherung/Einschränkung
Constraints:
Zusicherungen und Einschränkungen in Diagrammen
Mit Hilfe von Zusicherungen bzw. Einschränkungen (constraints) können wichtige Details – sprachabhängig oder -unabhängig – Diagrammen hinzugefügt werden.
|
Zusicherungen werden in geschweifte Klammern {...} gefasst. |
Nachfolgend Zusicherungen, die häufig verwendet werden:
{abstract}: Klasse hat keine Objekte (siehe auch Abb. 4.15) {disjoint}: Subklassen sind disjunkt, haben keine Objekte gemeinsam {incomplete}: Unvollständigkeit der Darstellung {overlapping}: Subklassen nicht disjunkt {subset}: Assoziation ist Untermenge einer anderen
Zum Beispiel sollten die drei Subklassen, stellvertretend für alle PKW-Hersteller in Abb. 4.2, durch {incomplete} gekennzeichnet werden.
Zusicherungen können aber auch frei gewählte Bedingungen enthalten:
{wert>=10}
Stereotyp
UML-Anpassung an Java mittels Stereotype
Durch Stereotype wird die UML anpassbar. Stereotype erweitern oder variieren vorhandene Modell-Elemente:
|
Ein Stereotyp ist ein in «..» eingerahmter Begriff, der nicht direkt zum Sprachumfang der UML gehört, aber für die Anwendung, das Modell oder die Programmiersprache notwendig ist. |
Für Java bieten sich die in der UML unbekannten Modifikatoren oder der Begriff »Interface« als Stereotyp an.
Beispiele bekannter und in Java verwendeter Stereotype sind:
«constructor», «implements», «interface», «native»
Ein weiteres, interessantes Stereotyp ist «Powertype». Bei der Generalisierung werden die Mengen aller Objekte der Subklassen in der Superklasse zusammengefasst.
«Powertype»-Klassen führen zu Spezialisierungen (Teilmengen)
Damit beschreibt ein Diskriminator verbal eine Teilmenge der Potenzmenge (Powerset)12 der Superklasse. Ein Diskriminator kann so als spezielle «Powertype»-Klasse gezeichnet oder einfach an eine gepunktete Linie geschrieben werden (Abb. 4.15).
Abbildung 4.15 Zwei verschiedene Teile-Spezialisierungen
Die Klasse Teil ist in Abb. 4.15 mit der Zusicherung {abstract} gekennzeichnet. Es gibt keine Objekte von Teil, sondern nur von den Subklassen. Je nach Diskriminator werden zwei Spezialisierungen vorgenommen.
Die Spezialisierung nach Fremd- oder Eigenbezug ist nicht disjunkt, da dies z.B. von der benötigten Menge oder den Kapazitäten der Fertigung abhängt. Die Spezialisierung nach dem Strukturierungsgrad ist disjunkt, beginnend bei unbearbeitetem Material (Roh- oder Hilfsstoffen) bis hin zum Erzeugnis, dem Endprodukt.
Interface
Interface: Definition eines Services
Interfaces (Schnittstellen) werden in der UML mittels des Stereotyps «interface» gekennzeichnet, da sie sprachabhängig sind. In Java gehören Interfaces zum Sprachumfang, in C++ müssen sie mit Hilfe abstrakter Klassen nachgebildet werden.
Fünf wichtige Eigenschaften eines Interfaces in der UML
Die Definition eines Interfaces ist in der UML und Java sehr ähnlich:
1. |
In der UML ist ein Interface eine benannte Kollektion von (public) Methoden ohne Implementierung, die einen Service bezeichnet, den eine Klasse anderen anbieten kann. |
2. |
Eine Klasse implementiert ein Interface, indem es alle Methoden des Interfaces deklariert und auch implementiert (Notation: gestrichelter Generalisierungspfeil, optional mit Stereotyp «implements»). |
3. |
Eine Klasse kann mehrere Interfaces implementieren. |
4. |
Ein Interface kann spezialisiert, d.h. erweitert werden, wobei das Sub-Interface die Methoden des Super-Interfaces implizit übernimmt. |
5. |
Interfaces können an Assoziationen beteiligt sein, wobei eine Navigation, die vom Interface startet, in der UML nicht erlaubt ist. |
Interface: Unterschiede der UML zu Java
Die UML erlaubt keine Attribute in Interfaces, wogegen Java konstante statische (public static final) Felder im Interface erlaubt.
Interfaces verbergen Klassen
Interfaces werden in Java benutzt, um Klassen zu entkoppeln. Klassen benutzen andere Klassen nur über ihr Interface, d.h., Klassen »verbergen« sich hinter ihrem Interface.13
Dies wird durch zwei Notationen in der UML unterstützt:
|
Ein Lollipop-Symbol an der Klasse mit Namen des Interfaces |
Abbildung 4.16 Lollipop-Notation für Kundenaufträge
Interfaces haben Rollen-Charakter
Eine Assoziations-Linie mit dem Zusatz Rollenname:Interface-Name oder vereinfacht uses:Interface-Name
Abbildung 4.17 Alternative Benutzung in der Form uses:Interface
Interfaces werden ausführlich in Kapitel 6, Interfaces und Pattern, behandelt (siehe insbesondere das Teile-Beispiel in 6.11.1).
4.3 Objekt-Diagramm ![down](common/down.gif) ![top](common/top.gif)
Klassen vs. Objekte
Die bisher verwendete Klassen-Notation bzw. die zugehörigen Klassen-Diagramme zeigen nur den Bauplan für Objekte bzw. die Beziehungen zwischen Mengen von Objekten.
In der Interaktions-Modellierung (siehe 4.4) werden aber Objekte statt Klassen verwendet.
4.3.1 Objekt-Notation ![down](common/down.gif) ![top](common/top.gif)
Die Objekt-Notationen mit einem zugehörigen Beispiel sind in Abb. 4.18 dargestellt.
Vier Objekt-
Notationen mit Beispielen
Abbildung 4.18 Notationen für Objekte
Je nach Typ des Diagramms, das Objekte verwendet, wird einer der vier in der Abbildung dargestellten Notationen bevorzugt.
Die Angabe eines Objekt-Zustands ist optional, wird aber häufig bei Prozess- oder Aktivitäts-Diagrammen benutzt.
4.3.2 Objekt-Diagramme ![down](common/down.gif) ![top](common/top.gif)
Objekt-Diagramm: Momentaufnahme von Klassen-
Beziehungen
Objekt-Diagramme sind Momentaufnahmen von Klassen-Diagrammen. Sie verdeutlichen anhand einzelner Objekte und deren Verbindungen (Links) ein Klassen-Diagramm. Dabei sind die Verbindungen Instanzen einer Assoziation(s-Linie) und haben deshalb auch keine Multiplizität.
In Abb. 4.19 wird ein Klassen-Diagramm eines Unternehmens und in Abb. 4.20 ein zugehöriges Organigramm als Objekt-Diagramm gezeigt.
Abbildung 4.19 Unternehmen mit drei Sparten
Abbildung 4.20 Organigramm als Objekt-Diagramm
4.4 Interaktions-Modellierung ![down](common/down.gif) ![top](common/top.gif)
Interaktions-Diagramme: Beschreibung des dynamischen Verhaltens von Objekten
Klassen- wie Objekt-Diagramme sind statische Diagramme. Sie zeigen kein dynamisches Verhalten, d.h. weder den Ablauf von Prozessen, die Interaktion zwischen Objekten aufgrund ihrer Methoden noch das individuelle Verhalten einzelner Objekte.
In der UML gibt es verschiedene Arten von Interaktions-Diagrammen:
|
Zustands-Diagramm (für einzelne Objekte) |
|
Sequenz-Diagramm (für Interaktion zwischen Objekten) |
|
Kollaborations-Diagramm (für komplexe Interaktionen) |
Nachfolgend wird nur das Sequenz-Diagramm vorgestellt, da es u.a. vorzüglich für einfache Interaktionen zwischen verschiedenen Objekten geeignet ist (siehe zu Threads Kapitel 9).
4.4.1 Sequenz-Diagramme ![down](common/down.gif) ![top](common/top.gif)
Sequenz-Diagramme visualisieren die Sequenz, in der Objekte miteinander durch Aufruf von Methoden agieren.
Sequenz-Diagramme:
Zeitlicher Ablauf der Nachrichten bzw. Methoden
Die Methodenaufrufe werden auch als »Senden von Nachrichten« (sending messages) bezeichnet, die Resultate der Methoden als »Erhalten von Anworten« (receiving answers).
Die Objekte werden waagerecht angeordnet und die Interaktionen entlang einer Lebenslinie senkrecht unter den Objekten eingetragen (siehe Abb. 4.21).
Notation zu Sequenz-Diagrammen
Abbildung 4.21 Synchrone Objekt-Nachrichten
Darstellung der Programm-
Steuerung
Hat ein Objekt die Steuerung des Programms, z.B. durch Ausführen von main(), so kann man auch einen durchgezogenen Aktivierungsbalken zeichnen, obwohl das Objekt bei einem (synchronen) Aufruf einer Methode warten muss. Dies macht die Zeichnung einfacher.
Beispiel
Zu einem kleinen Code-Fragment soll ein passendes Sequenz-Diagramm gezeichnet werden. Dazu wird eine Klasse Teil angelegt, wobei zur Vereinfachung ein Konstruktor verwendet wird (siehe Abschnitt 5.5):
class Teil {
String id; // Ident des Teils
String bez; // Bezeichnung
// zu Konstruktor: siehe 5. Kapitel
Teil(String id, String bez) { this.id=id; this.bez=bez; }
}
Vom Java-Code zum Diagramm
Die Klasse Test hat die Programmkontrolle:
public class Test {
public static void main(String[] args) {
Teil[] tarr= { new Teil("4-01-001","Pumpe"),
new Teil("0-45-070","Lager"),
new Teil("3-71-101","Bohrer")};
Map map= new HashMap(); // eine Map, siehe
4.2.5
for (int i=0;i<tarr.length;i++)
// Teil unter Schlüssel teil.id ablegen
map.put(tarr[i].id,tarr[i]);
tarr= null; // kann zerstört werden
// Teil mit Schlüssel "0-45-070" holen
Teil teil= (Teil) map.get("0-45-070");
System.out.println(teil.id+": "+teil.bez);
map= null; // kann zerstört werden
//...
}
}
Abbildung 4.22 Sequenz-Diagramm zur Klasse Test
Multi-Objekt: Menge von Objekten, auf die eine Methode ausgeführt wird
Ein Multi-Objekt-Symbol wird hier verwendet um anzuzeigen, dass new() zu einer Menge von (unbenannten) Teilen führt.
Zerstörung von Objekten ist einzig Aufgabe der JVM
Java kennt keine Destruktoren. Die Zerstörungen am Ende der Lebenslinien sind fiktiv, d.h. bleiben der Garbage Collection (Speicherbereinigung) der JVM überlassen (siehe hierzu 5.9.1).
4.5 Zusammenfassung ![top](common/15x15leer.gif) ![top](common/top.gif)
Generalisierung/Spezialisierung – die Is-A-Beziehung – ist der Ausgangspunkt für die Vererbung in Java. Hieraus leiten sich Klassifikation als ein Design-Prinzip und Substitution als zentrale Regel ab.14
Mit Hilfe von Vererbung und Polymorphie können generelle Methoden geschrieben werden, die – abhängig vom konkreten Objekt – die passende Operation ausführen.
Die UML ist eine zu Java komplementäre grafische Design-Methodik, die umfangreiche Werkzeuge zur Modellierung bereitstellt.
Anhand von Diagrammen mit zugehörigen kleinen Code-Fragmenten in Java wurden Klassen-, Objekt- und Sequenz-Diagramme vorgestellt.
Klassen/Objekt-Diagramme zeigen die statischen Beziehungen zwischen Klassen, wobei die Assoziation die häufigste und damit die wichtigste Beziehungsform darstellt.
Assoziationen werden in Java durch Referenzen realisiert, wobei sie die Navigationsrichtungen vorgeben. Komposition kann nur durch Design sichergestellt werden, da die Referenz-Semantik in Java nur Aggregation direkt unterstützt.
Von den Interaktions-Diagrammen, die das dynamische Verhalten eines oder mehrerer Objekte beschreiben, wurden die Sequenz-Diagramme vorgestellt, die den zeitlichen Ablauf und die Interaktion der Objekte recht gut darstellen.
Am letzten Code-Beispiel bzw. Diagramm (Abb. 4.22) wird aber auch deutlich, dass Sequenz-Diagramme nur für die Darstellung von einfachen Interaktionen geeignet sind, da die Modellierung recht schnell groß und unübersichtlich wird.
1 Hier ändert auch der Komponenten-Markt zurzeit nicht allzu viel, da er zu sehr an die Sprachen und Betriebssysteme gebunden ist.
2 Das Vererbungsmuster à la Java ergibt einen Baum (mit genau einer Wurzel), im Gegensatz zu C++, welches Mehrfachvererbung mit beliebig vielen Wurzeln zulässt.
3 Potenziell deshalb, weil man natürlich von einer Klasse auch kein oder nur ein Objekt erzeugen kann.
4 Unter Verhalten versteht man die Reaktion auf die Umwelt. Eigenschaften halten die Werte der Beschreibung eines Objekts fest.
5 Korollar: Folgerung aus einem bewiesenen Satz.
6 Generalisierung und Spezialisierung sind synonym.
7 Erklärung siehe weiter unten.
8 Sofern der Konzern eine Stab-Linien-Organisation hat.
9 Zu Kollektionen siehe Kapitel 14
10 Hier ist nicht modelliert, dass Frame selbst auch eine Komponente ist.
11 Vgl. zu dieser Problematik auch Abschnitt 8.4.3 und 9.8.3.
12 Potenzmenge: Menge PM, deren Elemente alle möglichen Teilmengen der betrachteten Menge M sind. Besteht M aus n Elementen, enthält die Potenzmenge PM 2n Elemente.
13 Die Idee ist äquivalent zu der von Microsofts Component Object Model (COM).
14 Der Zusammenfassung folgen keine Testfragen, da sie konzeptionell zur UML und nicht zu Java gehören. Die UML gehört nicht zur Java-Zertifizierung.
|