Implementationsdiagramme
In der Einführungsphase hast du Klassendiagramme gezeichnet, um deine Entwürfe festzuhalten. Sie waren eine Skizze: „Es gibt ein Konto, das hat einen Besitzer und einen Kontostand."
Für eine Skizze reicht das. Für eine Klassenarbeit nicht – und für jemanden, der deinen Entwurf umsetzen soll, auch nicht. Denn aus „hat einen Kontostand" folgt weder, ob der Kontostand ein int oder ein double ist, noch ob man ihn von außen verändern darf.
Zwei Stufen der Genauigkeit
- Ein Entwurfsdiagramm entsteht früh im Modellierungsprozess. Es nennt Klassen und Beziehungen, oft ohne Datentypen und ohne Sichtbarkeiten. Es beantwortet die Frage: Woraus besteht das System?
- Ein Implementationsdiagramm ist vollständig. Es nennt zu jedem Attribut den Datentyp, zu jeder Methode die Parameter mit Typ und den Rückgabetyp, und zu jedem Element die Sichtbarkeit. Es beantwortet die Frage: Wie sieht der Quelltext aus?
Aus einem Implementationsdiagramm lässt sich das Klassengerüst ohne Rückfragen schreiben – und umgekehrt. Genau das ist der Prüfstein: Wer beim Übersetzen ins Java noch etwas erfinden muss, hat kein Implementationsdiagramm vor sich.
Weitere Darstellungsformen findest du unter Objektorientierte Modellierung.
Die Notation
classDiagram
class Konto {
-besitzer: String
-kontostand: double
+Konto(pBesitzer: String)
+getBesitzer() String
+getKontostand() double
+zahleEin(pBetrag: double)
+hebeAb(pBetrag: double) boolean
#korrigiere(pNeuerStand: double)
}
Ein Klassenkasten hat drei Felder: Name, Attribute, Methoden.
| Zeichen | Bedeutung | in Java |
|---|---|---|
- |
nur in dieser Klasse sichtbar | private |
# |
auch in den Unterklassen sichtbar | protected |
+ |
überall sichtbar | public |
Geschrieben wird
- ein Attribut als
sichtbarkeit name: typ, - eine Methode als
sichtbarkeit name(parameter: typ): rückgabetyp.
Der Datentyp steht also immer hinter dem Doppelpunkt – beim Attribut, beim Parameter und beim Rückgabewert.
Ein Konstruktor heißt wie die Klasse und hat keinen Rückgabetyp. Eine Methode ohne Rückgabewert bekommt auch keinen: Bei ihr endet die Zeile hinter der Klammer, das void aus dem Java-Quelltext taucht im Diagramm nicht auf.
Die Reihenfolge name: typ ist die in Nordrhein-Westfalen übliche und zugleich die der UML. In manchen Büchern steht stattdessen typ name – so herum, wie es im Java-Quelltext aussieht. Beides meint dasselbe; halte dich an die Schreibweise, die deine Lehrkraft benutzt, und mische sie nicht.
Beziehungen gehören dazu
Ein Diagramm mit einer einzigen Klasse ist selten. Sobald mehrere Klassen zusammenspielen, gehören auch die Beziehungen hinein.
classDiagram
class Bank {
-name: String
-konten: Konto[]
-anzahl: int
+Bank(pName: String, pMaxKonten: int)
+eroeffne(pKonto: Konto) boolean
+gesamtvermoegen() double
}
class Konto {
-besitzer: String
-kontostand: double
+Konto(pBesitzer: String)
+getKontostand() double
}
class Girokonto {
-dispolimit: double
+Girokonto(pBesitzer: String, pDispolimit: double)
}
Bank "1" --> "0..*" Konto : verwaltet
Konto <|-- Girokonto
| Linie | Bedeutung | im Quelltext |
|---|---|---|
| durchgezogener Pfeil mit offener Spitze | Assoziation – „kennt", „hat" | ein Attribut vom Typ der anderen Klasse |
| durchgezogener Pfeil mit leerem Dreieck | Vererbung – „ist ein" | extends |
An eine Assoziation schreibt man die Kardinalität: 1 an das eine Ende, 0..* an das andere heißt „eine Bank verwaltet beliebig viele Konten, jedes Konto gehört zu genau einer Bank".
Geerbte Attribute und Methoden werden im Unterklassenkasten nicht wiederholt – dafür ist der Pfeil da. Im Kasten steht nur das, was neu dazukommt oder überschrieben wird.
Vom Diagramm zum Quelltext
Das Diagramm oben ergibt Zeile für Zeile diesen Quelltext. Vergleiche beim Lesen jede Java-Zeile mit ihrer Entsprechung im Kasten.
Was das Diagramm nicht sagen kann. Dass negative Beträge ignoriert werden und dass nur abgehoben werden darf, was da ist, steht nirgends im Kasten – nur im Quelltext und im Kommentar darüber.
Deshalb gehört zu einem Entwurf immer beides: das Diagramm für den Aufbau und eine kurze Beschreibung je Methode für die Bedeutung. Genau das nennt der Lehrplan „Klassen dokumentieren".
Teil 1: Lesen
Aufgabe 1: Vom Quelltext zum Diagramm
Ohne Rechner. Zeichne auf Papier das vollständige Implementationsdiagramm zur folgenden Klasse. Trage alles ein, was hineingehört – und nur, was hineingehört.
public class Spielfigur {
private String name;
private int leben;
private double x;
private double y;
protected boolean unverwundbar;
public Spielfigur(String pName) {
name = pName;
leben = 3;
unverwundbar = false;
}
public String getName() {
return name;
}
public int getLeben() {
return leben;
}
public void bewege(double pDx, double pDy) {
x = x + pDx;
y = y + pDy;
}
public boolean erleideSchaden(int pMenge) {
if (unverwundbar) {
return false;
}
leben = leben - pMenge;
pruefeLeben();
return true;
}
private void pruefeLeben() {
if (leben < 0) {
leben = 0;
}
}
}
Tipp: die vier Stolpersteine
Geh die Klasse Zeile für Zeile durch und frag dich bei jeder Zeile: Steht das schon in meinem Kasten?
Vier Dinge werden regelmäßig vergessen: eine Methode, ein Sichtbarkeitszeichen, ein paar Datentypen und die Klammern hinter einem Namen.
Aufgabe 2: Diagramm und Quelltext passen nicht zusammen
Ohne Rechner. Jemand hat zuerst das Diagramm gezeichnet und dann den Quelltext geschrieben – dabei sind vier Abweichungen entstanden.
Finde alle vier. Notiere zu jeder: Was steht im Diagramm, was im Quelltext? Entscheide danach für jede Abweichung, welche der beiden Seiten du ändern würdest, und begründe es in einem Satz.
classDiagram
class Buch {
-titel: String
-seiten: int
-ausgeliehen: boolean
+Buch(pTitel: String, pSeiten: int)
+getTitel() String
+getSeiten() int
+istAusgeliehen() boolean
+leiheAus() boolean
+gibZurueck()
-protokolliere(pAktion: String)
}
public class Buch {
private String titel;
public int seiten;
private boolean ausgeliehen;
public Buch(String pTitel, int pSeiten) {
titel = pTitel;
seiten = pSeiten;
ausgeliehen = false;
}
public String getTitel() {
return titel;
}
public boolean istAusgeliehen() {
return ausgeliehen;
}
public boolean leiheAus() {
if (ausgeliehen) {
return false;
}
ausgeliehen = true;
return true;
}
public void gibZurueck(String pName) {
ausgeliehen = false;
}
public void protokolliere(String pAktion) {
IO.println(pAktion);
}
}
Tipp: geh in vier Runden vor
Vergleiche nicht alles auf einmal, sondern viermal die ganze Klasse:
- Gibt es jedes Element beider Seiten auf der anderen Seite auch?
- Stimmen die Sichtbarkeiten?
- Stimmen die Datentypen – bei Attributen, Rückgaben und Parametern?
- Stimmen die Parameterlisten?
Teil 2: Schreiben
Aufgabe 3: Vom Diagramm zum Quelltext
Setze das folgende Implementationsdiagramm um, bis alle Tests grün sind. Achte auf jede Angabe: Sichtbarkeiten, Datentypen, Rückgabetypen.
classDiagram
class Rechteck {
-breite: double
-hoehe: double
+Rechteck(pBreite: double, pHoehe: double)
+getBreite() double
+getHoehe() double
+flaeche() double
+umfang() double
+istQuadrat() boolean
+verdoppleSeiten()
#skaliere(pFaktor: double)
}
Was ein Diagramm nicht sagen kann und deshalb hier danebensteht:
- Der Konstruktor setzt Seiten, die kleiner oder gleich 0 sind, auf 1.
istQuadratlieferttrue, wenn beide Seiten gleich lang sind.skalieremultipliziert beide Seiten mit dem Faktor – aber nur, wenn dieser positiv ist.verdoppleSeitenbenutztskalieremit dem Faktor 2 und rechnet nicht selbst.
Tipp 1: die Attribute
Im Diagramm stehen zwei Zeilen mit -. Daraus wird im Quelltext:
private double breite;
Die zweite schreibst du selbst.
Tipp 2: skaliere ist protected
# im Diagramm heißt protected. Das Gerüst gibt das schon so vor – ändere es nicht auf public.
Genau deshalb ruft der Test skaliere auch nicht direkt auf: Von außen ist die Methode gar nicht sichtbar. Er geht über verdoppleSeiten(), und diese Methode ruft von innen auf:
skaliere(2.0);
Das ist der übliche Umgang mit protected und private: Solche Methoden sind Werkzeuge der Klasse für sich selbst und für ihre Unterklassen.
Tipp 3: die Mindestseite
Für jede Seite dieselbe Prüfung, zweimal hingeschrieben:
if (pBreite <= 0) {
breite = 1.0;
} else {
breite = pBreite;
}
Zum Weiterdenken
Vertiefung 1: Was ein Diagramm verschweigt
Zwei Programmiererinnen bekommen dasselbe Implementationsdiagramm von Rechteck – ohne die drei Sätze, die in Aufgabe 3 danebenstanden. Beide setzen es korrekt um, und ihre Klassen verhalten sich trotzdem verschieden.
a) Nenne drei Stellen, an denen die beiden Umsetzungen auseinandergehen können, obwohl beide zum Diagramm passen.
b) Ein Diagramm kann diese Lücke grundsätzlich nicht schließen. Warum nicht? Was wäre der Preis, wenn man es versuchte?
c) Womit schließt man sie stattdessen? Nenne zwei Mittel, die du in diesem Lernpfad schon benutzt hast.
d) Beurteile: Wenn man die Lücke ohnehin anders schließen muss – wozu dann überhaupt ein Diagramm?
Vertiefung 2: Der eigene Entwurf im Rückspiegel
Nimm dir das Spiel oder das Projekt vor, das du am Ende der Einführungsphase gebaut hast.
a) Zeichne nachträglich das vollständige Implementationsdiagramm – mit allen privaten Methoden, allen Datentypen und allen Beziehungen zwischen den Klassen.
b) Vergleiche es mit dem Entwurf von damals. Was ist beim Programmieren dazugekommen, was hast du weggelassen?
c) Suche im Diagramm nach den Stellen, die dir jetzt nicht mehr gefallen: ein public, das private sein könnte; eine Klasse, die zu viel weiß; eine Methode, die in der falschen Klasse steht.
d) Beurteile: Hätte ein genaueres Diagramm dir Arbeit erspart – oder wärst du damit nur langsamer losgekommen? Es gibt auf diese Frage keine allgemein richtige Antwort, aber eine begründete.