Lektion 4 von 7

Klassendiagramm II: Vererbung, Komposition, Code

In Lektion 3 hast du Klassen und Assoziationen gezeichnet. Jetzt kommen die Beziehungen, an denen in der Prüfung die meisten Punkte hängen: Vererbung, abstrakte Klassen, Interfaces, Aggregation und Komposition. Zum Schluss übersetzt du ein Diagramm in Python und Java.

  • Etwa 30 Minuten
  • 2 Zeichenaufgaben, 5 Quizfragen
  • Papier und Stift genügen

Vererbung

Vererbung heißt in UML Generalisierung (generalization). Eine Unterklasse übernimmt alle Attribute und Methoden ihrer Oberklasse und ergänzt eigene. Du prüfst sie mit dem Satz „ist ein“: Ein Buch ist ein Medium, eine DVD ist ein Medium. Passt der Satz nicht, etwa „eine Ausleihe ist ein Leser“, ist es keine Vererbung.

Gezeichnet wird eine durchgezogene Linie mit hohlem Dreieck an der Oberklasse. Mehrere Unterklassen dürfen ihre Linien zu einem gemeinsamen Dreieck zusammenführen, das macht das Diagramm ruhiger. In der Unterklasse stehen nur die neuen Attribute; geerbte schreibst du nicht noch einmal hin. Überschreibt die Unterklasse eine Methode, führst du diese Methode dort erneut auf.

«interface»Verlaengerbar+ verlaengern(tage: int): voidMedium{abstract}# medienNr: int# titel: String+ berechneLeihfrist(): intBuch- isbn: String- seiten: int+ berechneLeihfrist(): int+ verlaengern(tage: int): voidDVD- laufzeitMin: int- fsk: int+ berechneLeihfrist(): int
Medium ist abstrakt, Buch und DVD erben davon. Buch realisiert zusätzlich das Interface Verlaengerbar.

Die Attribute von Medium sind hier geschützt (#), damit Buch und DVD direkt darauf zugreifen können. Private Attribute werden zwar auch vererbt, sind in der Unterklasse aber nur über Methoden der Oberklasse erreichbar. Beides ist in der Prüfung richtig, solange du es einheitlich machst.

Abstrakte Klassen und Interfaces

Abstrakte Klasse

Eine abstrakte Klasse (abstract class) ist ein Bauplan, von dem es selbst keine Objekte gibt. In der Bibliothek steht nie „ein Medium“ im Regal, sondern immer ein Buch oder eine DVD. Du kennzeichnest sie mit einem kursiven Namen oder mit {abstract} unter dem Namen. Weil Kursivschrift von Hand kaum erkennbar ist, schreibst du in der Prüfung auf Papier besser {abstract}.

Eine abstrakte Klasse darf abstrakte Methoden haben: Die Signatur steht fest, der Inhalt fehlt. Jede Unterklasse, von der es Objekte geben soll, muss sie implementieren. Im Beispiel berechnet jede Medienart ihre Leihfrist anders, darum ist berechneLeihfrist() in Medium abstrakt und kursiv gesetzt; von Hand hängst du auch hier {abstract} an.

Interface

Ein Interface (Schnittstelle) ist ein reiner Vertrag: eine Liste von Methoden, die eine Klasse anbieten muss, in der Regel ohne Attribute und ohne Umsetzung. Es trägt das Stereotyp «interface» über dem Namen. Die Beziehung zur umsetzenden Klasse heißt Realisierung (realization): eine gestrichelte Linie mit hohlem Dreieck am Interface. Im Beispiel lassen sich nur Bücher verlängern, DVDs nicht; deshalb realisiert nur Buch das Interface Verlaengerbar.

Aggregation und Komposition

Beide sind Sonderformen der Assoziation für Teil-Ganzes-Beziehungen („hat ein“, „besteht aus“). Die Raute sitzt immer am Ganzen. Der Unterschied liegt in der Existenzabhängigkeit: Was passiert mit den Teilen, wenn das Ganze gelöscht wird?

KompositionAggregation11..*0..*1..*Bestellung- bestellNr: int- datum: Date+ berechneSumme(): doubleBestellposition- menge: int- einzelpreis: double+ berechneBetrag(): doubleTeam- name: StringMitarbeiter- personalNr: int- name: String
Links Komposition mit gefüllter Raute, rechts Aggregation mit hohler Raute.
  • Komposition (composition, gefüllte Raute): Eine Bestellposition existiert nur als Teil ihrer Bestellung. Wird die Bestellung gelöscht, sind ihre Positionen ebenfalls weg; allein ergeben sie keinen Sinn. Ein Teil gehört zu genau einem Ganzen, darum steht am Ganzen 1 (höchstens 0..1).
  • Aggregation (aggregation, hohle Raute): Ein Team besteht aus Mitarbeitern, aber löst sich das Team auf, arbeiten die Mitarbeiter weiter in der Firma. Ein Mitarbeiter kann zudem in mehreren Teams sein, am Ganzen darf also 0..* stehen.

Die Frage aus der Aufgabe lautet also: Überlebt das Teil das Ganze? Nein: Komposition. Ja: Aggregation. Achte auf Formulierungen wie „wird mit … gelöscht“, „existiert nur als Teil von …“ oder „kann auch ohne … bestehen“.

Assoziationsklasse in Kürze

Manchmal hat eine Beziehung selbst Daten. Leser und Medium sind verbunden, aber Ausleih- und Rückgabedatum gehören weder zum Leser noch zum Medium, sondern zur Verbindung. Dafür gibt es die Assoziationsklasse (association class): eine normale Klasse, die über eine gestrichelte Linie an der Mitte der Assoziation hängt.

0..*0..*Leser- leserNr: int- name: StringMedium- medienNr: int- titel: StringAusleihe- ausleihdatum: Date- rueckgabedatum: Date+ verlaengern(tage: int): void

Eine Feinheit: Eine Assoziationsklasse erlaubt standardmäßig nur eine Verbindung pro Paar. Ein Leser könnte dasselbe Medium also nicht zweimal ausleihen. Deshalb war die Ausleihe in Lektion 3 eine eigene Klasse mit zwei Assoziationen; das ist die sichere Lösung, wenn eine Verbindung sich wiederholen kann.

Vom Diagramm zum Code

Viele Prüfungsaufgaben verlangen den Weg in eine Richtung: „Implementieren Sie die Klasse … gemäß dem Klassendiagramm“ oder „Erstellen Sie zum folgenden Code ein Klassendiagramm“. Die Aufgaben zeigen meist Java- oder C#-ähnlichen Pseudocode, darum siehst du hier beide Fassungen. Die Python-Grundlagen dazu findest du in Lektion 10 des Python-Kurses.

UMLPythonJava
Vererbung, Dreieck zur Oberklasseclass Buch(Medium):class Buch extends Medium
abstrakte Klasse, {abstract}class Medium(ABC): mit @abstractmethodabstract class Medium
Interface, «interface»ABC mit nur abstrakten Methodeninterface Verlaengerbar
Realisierung, gestricheltclass Buch(Medium, Verlaengerbar):implements Verlaengerbar
- privat / # geschützt / + öffentlich__name / _name / name (Konvention)private / protected / public
Multiplizität 1 oder 0..1einfaches Attribut, ggf. Noneeinfache Referenz, ggf. null
Multiplizität 0..* oder 1..*list[Bestellposition]List<Bestellposition>

Vererbung, abstrakte Klasse und Interface in Python

Python kennt kein Schlüsselwort für Interfaces. Abstrakte Klassen baust du mit dem Modul abc; eine Klasse, die nur abstrakte Methoden hat, spielt die Rolle des Interfaces. Methodennamen schreibt man in Python mit Unterstrichen (berechne_leihfrist), im Diagramm im camelCase. Beides ist in Ordnung.

from abc import ABC, abstractmethod

class Verlaengerbar(ABC):              # «interface»: nur abstrakte Methoden
    @abstractmethod
    def verlaengern(self, tage: int) -> None: ...

class Medium(ABC):                     # {abstract}: davon gibt es keine Objekte
    def __init__(self, medien_nr: int, titel: str):
        self._medien_nr = medien_nr    # # geschützt: ein Unterstrich
        self._titel = titel

    @abstractmethod
    def berechne_leihfrist(self) -> int: ...

class Buch(Medium, Verlaengerbar):     # erbt von Medium, realisiert Verlaengerbar
    def __init__(self, medien_nr: int, titel: str, isbn: str, seiten: int):
        super().__init__(medien_nr, titel)
        self.__isbn = isbn             # - privat: zwei Unterstriche
        self.__seiten = seiten

    def berechne_leihfrist(self) -> int:
        return 28

    def verlaengern(self, tage: int) -> None:
        print(f"{self._titel}: {tage} Tage verlängert")

class DVD(Medium):
    def __init__(self, medien_nr: int, titel: str, laufzeit_min: int, fsk: int):
        super().__init__(medien_nr, titel)
        self.__laufzeit_min = laufzeit_min
        self.__fsk = fsk

    def berechne_leihfrist(self) -> int:
        return 7

Dasselbe in Java

Java trennt sauber: extends für die eine Oberklasse, implements für Interfaces. Die Sichtbarkeiten stehen als Schlüsselwort vor jedem Attribut, die Typen vor dem Namen, also genau umgekehrt wie in UML.

public interface Verlaengerbar {
    void verlaengern(int tage);
}

public abstract class Medium {
    protected int medienNr;
    protected String titel;

    public Medium(int medienNr, String titel) {
        this.medienNr = medienNr;
        this.titel = titel;
    }

    public abstract int berechneLeihfrist();
}

public class Buch extends Medium implements Verlaengerbar {
    private String isbn;
    private int seiten;

    public Buch(int medienNr, String titel, String isbn, int seiten) {
        super(medienNr, titel);
        this.isbn = isbn;
        this.seiten = seiten;
    }

    @Override
    public int berechneLeihfrist() {
        return 28;
    }

    @Override
    public void verlaengern(int tage) {
        // Rückgabedatum um tage verschieben
    }
}

Komposition und Aggregation im Code

Der Code zeigt den Unterschied oft daran, wer das Teil erzeugt. Bei der Komposition erzeugt das Ganze seine Teile selbst, niemand sonst hält eine Referenz darauf. Bei der Aggregation wird ein bereits vorhandenes Objekt von außen übergeben und nur verknüpft.

class Bestellposition:
    def __init__(self, menge: int, einzelpreis: float):
        self.__menge = menge
        self.__einzelpreis = einzelpreis

    def berechne_betrag(self) -> float:
        return self.__menge * self.__einzelpreis

class Bestellung:
    def __init__(self, bestell_nr: int):
        self.__bestell_nr = bestell_nr
        self.__positionen: list[Bestellposition] = []   # 1..* Teile

    def neue_position(self, menge: int, einzelpreis: float) -> None:
        # Komposition: das Ganze erzeugt seine Teile selbst
        self.__positionen.append(Bestellposition(menge, einzelpreis))

    def berechne_summe(self) -> float:
        return sum(p.berechne_betrag() for p in self.__positionen)

class Mitarbeiter:
    def __init__(self, personal_nr: int, name: str):
        self.__personal_nr = personal_nr
        self.__name = name

class Team:
    def __init__(self, name: str):
        self.__name = name
        self.__mitglieder: list[Mitarbeiter] = []

    def aufnehmen(self, m: Mitarbeiter) -> None:
        # Aggregation: der Mitarbeiter existiert schon, er wird nur verknüpft
        self.__mitglieder.append(m)
public class Bestellung {
    private int bestellNr;
    private List<Bestellposition> positionen = new ArrayList<>();

    public void neuePosition(int menge, double einzelpreis) {
        // Komposition: das Ganze erzeugt seine Teile selbst
        positionen.add(new Bestellposition(menge, einzelpreis));
    }
}

public class Team {
    private String name;
    private List<Mitarbeiter> mitglieder = new ArrayList<>();

    public void aufnehmen(Mitarbeiter m) {
        // Aggregation: das Teil kommt fertig von außen
        mitglieder.add(m);
    }
}

Zeichenaufgaben

Zeichne erst selbst, dann klapp die Musterlösung auf und vergleiche Punkt für Punkt. Die Punkte sind Richtwerte im Stil der IHK; die tatsächliche Verteilung legt jede Prüfung selbst fest.

Zeichenaufgabe 4.1
Fahrzeugverleih, Richtwert 10 Punkte

Ein Fahrzeugverleih vermietet Pkw und Lastenräder. Jedes Fahrzeug hat eine Kennung und einen Tagespreis. Die Miete für eine Anzahl von Tagen wird für jede Fahrzeugart anders berechnet; ein allgemeines Fahrzeug ohne Art wird nie vermietet. Pkw haben zusätzlich ein Kennzeichen und eine Anzahl Sitzplätze, Lastenräder eine maximale Zuladung in kg und die Angabe, ob sie elektrisch unterstützt sind. Pkw sind versicherungspflichtig und müssen ihren Versicherungsbeitrag berechnen können. Da später auch Anhänger versichert werden sollen, die nicht vermietet werden, soll diese Fähigkeit über eine Schnittstelle Versicherbar modelliert werden.

Aufgabe: Erstellen Sie ein Klassendiagramm mit Attributen, Datentypen und Methoden. Stellen Sie Vererbung, abstrakte Elemente und die Schnittstelle in UML-Notation dar.

Zeichne auf Papier oder im Diagramm-Tool nachzeichnen: Es steckt in den Übungsprüfungen und öffnet sich dort bei jeder Zeichenaufgabe.

Musterlösung anzeigen
«interface»Versicherbar+ berechneBeitrag(): doubleFahrzeug{abstract}# kennung: String# tagespreis: double+ berechneMiete(tage: int): doublePkw- kennzeichen: String- sitzplaetze: int+ berechneMiete(tage: int): double+ berechneBeitrag(): doubleLastenrad- zuladungKg: int- elektrisch: boolean+ berechneMiete(tage: int): double

Fahrzeug ist abstrakt, weil nie „ein Fahrzeug“ vermietet wird, und berechneMiete() ist abstrakt, weil jede Art anders rechnet. Pkw und Lastenrad führen die Methode erneut auf, weil sie sie überschreiben. Die Versicherung ist ein Interface und kein Attribut in Fahrzeug: Lastenräder brauchen sie nicht, und künftig sollen auch Anhänger versichert werden, die keine Fahrzeuge der Vermietung sind. Private statt geschützter Attribute in Fahrzeug sind ebenfalls richtig.

So wird typischerweise bewertet

  • 2 Punkte: Fahrzeug als abstrakte Klasse ({abstract} oder kursiv) mit kennung und tagespreis.
  • 1 Punkt: berechneMiete(tage: int): double als abstrakte Methode in Fahrzeug.
  • 2 Punkte: Pkw und Lastenrad mit ihren eigenen Attributen, ohne die geerbten zu wiederholen.
  • 2 Punkte: Vererbungspfeile mit hohlem Dreieck an Fahrzeug.
  • 2 Punkte: Interface Versicherbar mit «interface» und berechneBeitrag(): double.
  • 1 Punkt: Realisierung gestrichelt von Pkw zu Versicherbar, nicht von Fahrzeug.
  • Richtwert gesamt: 10 Punkte.
Zeichenaufgabe 4.2
Rechnungen im Onlineshop, Richtwert 10 Punkte

Ein Onlineshop erstellt Rechnungen. Jede Rechnung hat eine Rechnungsnummer und ein Datum und gehört zu genau einem Kunden mit Kundennummer und Namen; ein Kunde kann beliebig viele Rechnungen erhalten. Eine Rechnung besteht aus mindestens einer Rechnungsposition mit Positionsnummer, Menge und dem Einzelpreis zum Zeitpunkt des Kaufs. Positionen existieren nur als Teil ihrer Rechnung und werden mit ihr gelöscht. Jede Position bezieht sich auf genau einen Artikel mit Artikelnummer, Bezeichnung und aktuellem Preis; Artikel bleiben im Sortiment, auch wenn Rechnungen gelöscht werden. Eine Rechnung soll neue Positionen anlegen und ihre Summe berechnen können, eine Position ihren Betrag.

Aufgabe: Erstellen Sie ein Klassendiagramm mit Attributen, Datentypen, Methoden und Multiplizitäten. Wählen Sie für jede Beziehung die passende Art und begründen Sie die Komposition in einem Satz.

Zeichne auf Papier oder im Diagramm-Tool nachzeichnen: Es steckt in den Übungsprüfungen und öffnet sich dort bei jeder Zeichenaufgabe.

Musterlösung anzeigen
10..*11..*10..*Kunde- kundenNr: int- name: StringRechnung- rechnungsNr: int- datum: Date+ neuePosition(a: Artikel, menge: int): void+ berechneSumme(): doubleRechnungsposition- positionNr: int- menge: int- einzelpreis: double+ berechneBetrag(): doubleArtikel- artikelNr: int- bezeichnung: String- preis: double

Nur die Beziehung zwischen Rechnung und Rechnungsposition ist eine Komposition, denn die Positionen werden mit der Rechnung gelöscht. Der Artikel bleibt im Sortiment, deshalb ist die Beziehung zur Position eine normale Assoziation. Der Einzelpreis steht bewusst in der Position: Ändert sich der Artikelpreis später, muss die alte Rechnung trotzdem den damaligen Preis zeigen. Begründungssatz zur Komposition (Musterlösung): „Rechnungspositionen existieren nur als Teil ihrer Rechnung und werden mit ihr gelöscht, sie sind existenzabhängig.“

So wird typischerweise bewertet

  • 1 Punkt: Klassen Kunde, Rechnung, Rechnungsposition und Artikel mit Attributen und Datentypen.
  • 2 Punkte: neuePosition(…) und berechneSumme() in Rechnung, berechneBetrag() in Rechnungsposition.
  • 2 Punkte: Komposition mit gefüllter Raute an Rechnung, nicht an der Position.
  • 2 Punkte: Multiplizitäten 1 zu 1..* an der Komposition und 1 zu 0..* zwischen Kunde und Rechnung.
  • 1 Punkt: normale Assoziation zwischen Rechnungsposition und Artikel mit 0..* zu 1.
  • 1 Punkt: Einzelpreis in der Rechnungsposition.
  • 1 Punkt: Begründung der Komposition über die Existenzabhängigkeit.
  • Richtwert gesamt: 10 Punkte.

Typische Fehler

Jetzt selbst testen

Fünf Fragen zur Notation. Du hast so viele Versuche, wie du willst.

1 / 5

Wie erkennst du im Klassendiagramm, dass Girokonto von Konto erbt?

2 / 5

Ein Haus wird abgerissen, seine Zimmer existieren danach nicht mehr. Welche Beziehung liegt zwischen Haus und Zimmer vor?

3 / 5

Welche Aussage über abstrakte Klassen ist richtig?

4 / 5

Welche Java-Zeile passt zu einer gestrichelten Linie mit hohlem Dreieck von Drucker zu «interface» Druckbar?

5 / 5

Ein Team besteht aus Mitarbeitern, die auch ohne das Team in der Firma bleiben und mehreren Teams angehören können. Welche Multiplizität steht an der hohlen Raute beim Team?