Block 08 · Objektorientierung & Entwurfsmuster

Meistens 6/9 Prüfungen · Ø 16 von 200 Punkten · Teil 1 (4×) + Teil 2 (2×) · Lösungen: 08_OOP_Entwurfsmuster_Loesungen.html
So arbeitest du mit diesem Block Erst den Erklärungssatz lesen, dann die Aufgaben mit dem Belegsatz daneben lösen (Belegsatz am besten separat ausdrucken und neben die Aufgaben legen). Zeitrichtwert: 1 Punkt ≈ 0,9 Minuten. Lösungen erst danach in der separaten Lösungsdatei nachsehen. Zu jeder Code-Aufgabe gibt es dort Pseudocode und eine lauffähige Java-Datei im Ordner java\08_OOP_Entwurfsmuster\.
In der Themenanalyse hat dieser Block nur zwei Unterthemen („OOP-Konzepte“ und „Entwurfsmuster“). Damit genug geübt wird, sind sie hier auf fünf Unterthemen verteilt. Die UML-Notation aller Diagrammarten im Detail behandelt Block 03; hier geht es um die objektorientierten Konzepte und ihre Umsetzung in Klassendiagramm, Pseudocode und Java.

A · Erklärungssatz

Objektorientierung kommt in der AP2 selten als „reine Theorie“ vor, sondern fast immer als Kette in einer größeren Aufgabe: Zuerst wird ein Klassendiagramm gezeichnet oder ergänzt (oft mit abstrakter Klasse oder Interface), dann soll ein Begriff wie Polymorphie erläutert werden, und zum Schluss werden einzelne Methoden in Pseudocode implementiert. Entwurfsmuster tauchen in derselben Form auf: Muster erkennen, im Klassendiagramm umsetzen, Ablauf zeigen, Methode programmieren. Beispiele: S22 Teil 1 Aufgabe 3 (Observer, 30 P), S23 Teil 1 Aufgabe 2b (Polymorphie und Fabrikmethode, 22 P), S26 Teil 2 Aufgabe 2 (abstrakte Klasse und dynamische Bindung, 20 P), W24 Teil 1 Aufgabe 4b (Entwurfsmuster, 10 P).

UnterthemaKategorieTypische PunkteWichtigste Fähigkeit
OOP-01 GrundkonzepteOft2–7 (Teil einer größeren Aufgabe)Klasse aus Text oder Diagramm umsetzen: Sichtbarkeiten, Konstruktor, static, Getter/Setter
OOP-02 Vererbung, abstrakte Klassen, InterfacesOft4–12Hierarchie modellieren, Fallunterscheidungen nach einem Typ-Attribut durch Vererbung ersetzen
OOP-03 Polymorphie, Überladen/Überschreiben, GenericsOft3–10Dynamische Bindung erklären, Aufrufe richtig auswerten, generische Klasse erläutern
OOP-04 Erzeugungsmuster und MusterkategorienWahrscheinlich2–14Vorteile von Mustern, Kategorien mit Beispielen, Singleton, Fabrikmethode
OOP-05 Verhaltens- und StrukturmusterWahrscheinlich4–30Observer vollständig (Klassendiagramm, Ablauf, Code), Muster zu einem Szenario wählen und begründen

Kategorien laut Themenanalyse: Der ganze Block kommt in 6 von 9 Prüfungen vor („Meistens“). Die Unterthemen OOP-01 bis OOP-03 gehören zu „OOP-Konzepte“ (5/9, Oft), OOP-04 und OOP-05 zu „Entwurfsmuster“ (3/9, Wahrscheinlich).

OOP-01 · Grundkonzepte: Klasse, Objekt, Kapselung Oft

Vorkommen: Teil von „OOP-Konzepte“ (5/9: S22, W22, S23, S24, S26) · ausdrücklich gefragt in S22 T1 A3a (Sichtbarkeiten im Klassendiagramm), S24 T1 A3bc (Objekt über überladenen Konstruktor erzeugen), S26 T2 A2a (Klassen mit vollständigen Signaturen und Sichtbarkeiten) · vorausgesetzt in jeder Algorithmusaufgabe mit Klassengerüst

Klasse und Objekt

Eine Klasse ist der Bauplan (Datentyp) für gleichartige Objekte. Sie legt fest, welche Attribute (Daten, Eigenschaften) und welche Methoden (Operationen, Verhalten) jedes Objekt hat. Ein Objekt (Instanz, Exemplar) ist ein konkretes Exemplar einer Klasse, das zur Laufzeit mit new erzeugt wird. Jedes Objekt hat eigene Attributwerte (seinen Zustand) und eine eigene Identität – zwei Objekte mit gleichen Werten sind trotzdem zwei verschiedene Objekte.

Konto- kontoNr : string- inhaber : string- saldo : double- dispo : double+ abheben(betrag : double) : boolean+ getSaldo() : doublek1 : KontokontoNr = "DE01"inhaber = "Yilmaz"saldo = 250.00dispo = 500.00k2 : KontokontoNr = "DE02"inhaber = "Novak"saldo = 0.00dispo = 0.00«instanceOf»«instanceOf»Klasse (Bauplan)Objekte (Instanzen)
Abb. A-1: Eine Klasse Konto, zwei Objekte mit eigenem Zustand (Objekte werden im UML-Objektdiagramm unterstrichen benannt)
BegriffBedeutungBeispiel (Konto)
AttributEigenschaft, die jedes Objekt der Klasse besitztsaldo : double
MethodeOperation, die ein Objekt ausführen kann; Aufruf = „Nachricht an das Objekt“abheben(betrag : double) : boolean
Zustandaktuelle Werte aller Attribute eines Objektsk1: Saldo 250,00 €, Dispo 500,00 €
Identitätjedes Objekt ist eindeutig, unabhängig von seinen Wertenk1 und k2 sind verschiedene Objekte
InstanziierungErzeugen eines Objekts mit new; dabei läuft der Konstruktork1 := new Konto("DE01", "Yilmaz")

Kapselung und Sichtbarkeiten

Datenkapselung (Geheimnisprinzip, information hiding) bedeutet: Die Attribute eines Objekts sind nach außen verborgen (private). Andere Objekte greifen nur über die öffentlichen Methoden zu. Dadurch entscheidet allein die Klasse, welche Änderungen erlaubt sind – ein Konto lässt sich nur über abheben verringern, und abheben prüft das Limit.

UMLJavaBedeutungeigene Klassegleiches PaketUnterklasseüberall
+publicöffentlich✓✓✓✓
#protectedgeschützt✓✓ (nur Java)✓–
~(kein Schlüsselwort)paketweit✓✓nur im selben Paket–
-privateprivat✓–––

In Prüfungsantworten genügt: protected = Zugriff in der Klasse selbst und in abgeleiteten Klassen (Unterklassen). Dass Java zusätzlich den Zugriff im selben Paket erlaubt, ist eine Java-Besonderheit.

MerkeFaustregel für jedes Klassendiagramm: Attribute - (private), Methoden + (public). Reine Hilfsmethoden, die nur intern gebraucht werden, sind -. Attribute, auf die Unterklassen direkt zugreifen sollen, sind #.
Vorteile der Kapselung: (1) Gültigkeit der Daten – Setter und Methoden prüfen Werte, bevor sie gespeichert werden; (2) Wartbarkeit – die interne Darstellung (z. B. Saldo in Cent statt Euro) kann geändert werden, ohne dass andere Klassen angepasst werden müssen; (3) geringe Kopplung – andere Klassen hängen nur von der öffentlichen Schnittstelle ab; (4) Fehler lassen sich leichter eingrenzen, weil nur die Klasse selbst ihren Zustand ändert.

Getter und Setter

Konstruktor, Überladen und this

Statische Attribute und Methoden (Klassenattribute, Klassenmethoden)

Ein mit static gekennzeichnetes Attribut gehört zur Klasse, nicht zu einem einzelnen Objekt. Es existiert genau einmal und wird von allen Objekten geteilt. Eine statische Methode wird über den Klassennamen aufgerufen (Konto.getAnzahlKonten()), auch wenn noch kein Objekt existiert. Sie hat kein this und kann deshalb nicht direkt auf Instanzattribute zugreifen.

Typische Falle bei staticWird ein Attribut, das je Objekt verschieden sein muss, versehentlich static deklariert (z. B. saldo), teilen sich alle Konten einen einzigen Saldo. Umgekehrt zählt ein nicht-statischer Zähler nur im eigenen Objekt und steht bei jedem neuen Objekt wieder auf dem Startwert.

Beispiel: vom Klassendiagramm zu Pseudocode und Java

Konto- anzahlKonten : integer- kontoNr : string {final}- inhaber : string- saldo : double- dispo : double+ Konto(kontoNr : string, inhaber : string, dispo : double)+ Konto(kontoNr : string, inhaber : string)+ setDispo(dispo : double) : void+ abheben(betrag : double) : boolean+ getSaldo() : double+ getAnzahlKonten() : integer
Abb. A-2: Klasse Konto mit statischem Zähler (unterstrichen), unveränderlicher Kontonummer und zwei Konstruktoren

Pseudocode

klasse Konto
  - anzahlKonten : integer := 0   {static}
  - kontoNr : string
  - inhaber : string
  - saldo : double
  - dispo : double

  konstruktor Konto(kontoNr : string,
                    inhaber : string, dispo : double)
    this.kontoNr := kontoNr
    this.inhaber := inhaber
    saldo := 0.0
    this.dispo := 0.0
    setDispo(dispo)
    anzahlKonten := anzahlKonten + 1
  Ende konstruktor

  konstruktor Konto(kontoNr : string, inhaber : string)
    this(kontoNr, inhaber, 0.0)
  Ende konstruktor

  methode setDispo(dispo : double) : void
    wenn dispo >= 0 und dispo <= 2000 dann
      this.dispo := dispo
    Ende wenn
  Ende methode

  methode abheben(betrag : double) : boolean
    wenn betrag > 0 und saldo - betrag >= -dispo dann
      saldo := saldo - betrag
      rückgabe true
    Ende wenn
    rückgabe false
  Ende methode

  methode getAnzahlKonten() : integer   {static}
    rückgabe anzahlKonten
  Ende methode
Ende klasse

Java (mit equals, siehe unten „Identität und Gleichheit“)

class Konto {
    private static int anzahlKonten = 0;
    private final String kontoNr;
    private String inhaber;
    private double saldo;
    private double dispo;

    public Konto(String kontoNr, String inhaber, double dispo) {
        this.kontoNr = kontoNr;
        this.inhaber = inhaber;
        this.saldo = 0.0;
        this.dispo = 0.0;
        setDispo(dispo);
        anzahlKonten = anzahlKonten + 1;
    }

    public Konto(String kontoNr, String inhaber) {
        this(kontoNr, inhaber, 0.0);
    }

    public void setDispo(double dispo) {
        if (dispo >= 0 && dispo <= 2000) {
            this.dispo = dispo;
        }
    }

    public boolean abheben(double betrag) {
        if (betrag > 0 && saldo - betrag >= -dispo) {
            saldo = saldo - betrag;
            return true;
        }
        return false;
    }

    public static int getAnzahlKonten() {
        return anzahlKonten;
    }

    public double getSaldo() {
        return saldo;
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof Konto && kontoNr.equals(((Konto) o).kontoNr);
    }
}

Aufruf: Konto k1 = new Konto("DE01", "Yilmaz", 500.0); und Konto k2 = new Konto("DE02", "Novak"); – danach liefert Konto.getAnzahlKonten() den Wert 2. k2.abheben(50.0) liefert false, weil k2 keinen Dispo hat.

Klassen in Pseudocode – Schreibweise in diesem Block

Der IHK-Belegsatz legt nur die Kontrollstrukturen fest. Für Klassen ist jede eindeutige Schreibweise zulässig. Diese wird in den Lösungen verwendet:

ElementPseudocodeJava
Klasseklasse Konto … Ende klassepublic class Konto { … }
Attribut / statisches Attribut- saldo : double / - anzahl : integer {static}private double saldo; / private static int anzahl;
Konstruktorkonstruktor Konto(nr : string) … Ende konstruktorpublic Konto(String nr) { … }
Methodemethode abheben(betrag : double) : boolean … Ende methodepublic boolean abheben(double betrag) { … }
Rückgaberückgabe saldo (auch return)return saldo;
eigenes Objekt / anderer Konstruktorthis.saldo / this(nr, 0.0)this.saldo / this(nr, 0.0);
Objekt erzeugenk : Konto := new Konto("DE01")Konto k = new Konto("DE01");
Methode aufrufenk.abheben(20.0), statisch: Konto.getAnzahlKonten()genauso
Vererbung / Interfaceklasse Girokonto erbt von Konto / klasse X implementiert Druckbarextends / implements
abstraktabstrakte klasse, abstrakte methode zinsen() : doubleabstract
Oberklassesuper(nr), super.zinsen()super(nr);, super.zinsen()
kein Objektnull, Prüfung wenn k <> null dannnull, if (k != null)
MerkeBewertet wird die Logik, nicht die Schreibweise. Ob du methode oder funktion, rückgabe oder return schreibst, ist egal – Hauptsache eindeutig und durchgehend gleich. Wenn die Aufgabe die Methode nur „in Pseudocode implementieren“ lässt, genügt die Methode selbst; die umgebende Klasse musst du nicht hinschreiben.

Referenzen, null, Identität und Gleichheit

Eine Variable vom Typ einer Klasse enthält nicht das Objekt selbst, sondern einen Verweis (Referenz) darauf. Eine Zuweisung k3 := k1 kopiert nur den Verweis: Danach zeigen beide Variablen auf dasselbe Objekt (Aliasing), und eine Änderung über k3 ist auch über k1 sichtbar. null bedeutet „zeigt auf kein Objekt“; ein Methodenaufruf auf null führt zur NullPointerException.

: KontokontoNr = "DE01"saldo = 250.00: KontokontoNr = "DE02"saldo = 0.00k1k3k2k4nullVariablen (Referenzen)
Abb. A-3: Drei Variablen, zwei Objekte – k1 und k3 verweisen auf dasselbe Objekt, k4 auf keins
VergleichprüftBeispiel (Abb. A-3, equals vergleicht die Kontonummer)
== bei ObjektenIdentität: Zeigen beide Referenzen auf dasselbe Objekt?k1 == k3 → true · k1 == k2 → false
equalsGleichheit: Sind die Objekte inhaltlich gleich? Nur wenn die Klasse equals überschreibt – sonst erbt sie Object.equals, das wie == arbeitet.Ein neues Objekt k5 mit der Kontonummer „DE01“: k1.equals(k5) → true, k1 == k5 → false
== bei int, double, booleanWertvergleich (primitive Typen haben keine Identität)3 == 3 → true

So gehst du vor: Klasse aus Text oder Klassendiagramm umsetzen

  1. Attribute sammeln: Jedes Substantiv mit einem Wert („Fachnummer“, „ob gesperrt“) wird ein Attribut mit passendem Typ (integer, double, string, boolean, eine Klasse oder List<…>). Sichtbarkeit -.
  2. Klassenweite Werte erkennen: „für alle Objekte gemeinsam“, „Anzahl aller …“, „ohne dass ein Objekt existiert“ → static (unterstrichen).
  3. Konstruktoren: Welche Werte müssen beim Erzeugen feststehen? Weitere Varianten mit Standardwerten als überladene Konstruktoren, die per this(…) den vollständigen Konstruktor aufrufen.
  4. Getter und Setter nur dort, wo sie gebraucht werden; Setter prüfen Werte; unveränderliche Werte bekommen keinen Setter.
  5. Fachmethoden: Name als Verb, Parameter mit Typ, Rückgabetyp. Vorbedingungen prüfen (wenn … dann), Zustand ändern, Erfolg melden (boolean) oder ein Objekt bzw. null zurückgeben.
  6. Beziehungen: Ein Attribut, dessen Typ eine andere Klasse ist, wird im Diagramm als (gerichtete) Assoziation zu dieser Klasse gezeichnet.
Typische Fehler
So wird es in der Prüfung gefragt

OOP-02 · Vererbung, abstrakte Klassen und Interfaces Oft

Vorkommen: Teil von „OOP-Konzepte“ (5/9) · S23 T1 A2bb (9 P: abstrakte Klasse mit Unterklassen statt Fallunterscheidungen), S23 T1 A2bd (4 P: überschreibende Methoden), S26 T2 A2a (12 P: abstrakte Klasse, Vererbung, Aggregation), S22 T1 A3bb/bc (6 P: Interface-Methoden ergänzen, Realisierung erläutern)

Vererbung (Generalisierung und Spezialisierung)

Vererbung ist eine „ist-ein“-Beziehung: Die Unterklasse (abgeleitete Klasse, Subklasse) übernimmt alle Attribute und Methoden der Oberklasse (Basisklasse, Superklasse), kann neue hinzufügen und geerbte Methoden überschreiben. Gemeinsamkeiten stehen nur einmal in der Oberklasse (Generalisierung), Besonderheiten in den Unterklassen (Spezialisierung).

Konstruktoren und super

Konstruktoren werden nicht vererbt. Jeder Konstruktor einer Unterklasse ruft als erste Anweisung einen Konstruktor der Oberklasse auf: ausdrücklich mit super(…) oder – wenn man nichts schreibt – automatisch mit super(). Das automatische super() funktioniert nur, wenn die Oberklasse einen parameterlosen Konstruktor hat. Der Oberklassenteil eines Objekts wird also immer zuerst initialisiert („von oben nach unten“).

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
        System.out.println("Person " + name + " angelegt");
    }
}

class Kunde extends Person {
    private int kundenNr;

    public Kunde(String name, int kundenNr) {
        super(name);                   // muss die erste Anweisung sein
        this.kundenNr = kundenNr;
        System.out.println("Kunde " + kundenNr + " angelegt");
    }
}

class Firmenkunde extends Kunde {
    private String firma;

    public Firmenkunde(String name, int kundenNr, String firma) {
        super(name, kundenNr);
        this.firma = firma;
        System.out.println("Firmenkunde " + firma + " angelegt");
    }
}

Aufruf und Ausgabe

new Firmenkunde("Kaya", 4711, "Kaya Bau GmbH");

Person Kaya angelegt
Kunde 4711 angelegt
Firmenkunde Kaya Bau GmbH angelegt

Mit super.methode() ruft eine Unterklasse außerdem die Oberklassen-Version einer überschriebenen Methode auf, z. B. rückgabe super.urlaubstage() + 2.

protected und final

Abstrakte Klassen

Interfaces (Schnittstellen)

Dokument- erstelltAm : Datum+ getErstelltAm() : Datum«interface»Exportierbar+ exportieren(format : string) : string«interface»Druckbar+ drucken() : voidRechnung- betrag : double+ drucken() : void+ exportieren(format : string) : stringMahnung- stufe : integer+ drucken() : void
Abb. A-4: Rechnung erbt von Dokument und realisiert zwei Interfaces; Mahnung realisiert nur Druckbar

Abstrakte Klasse oder Interface?

Abstrakte KlasseInterface
Beziehung„ist ein“ – gemeinsamer Oberbegriff einer Familie verwandter Klassen„kann“ – Fähigkeit/Rolle, auch für nicht verwandte Klassen
Objekte erzeugbar?neinnein
als Typ verwendbar?ja (Variable, Parameter, Liste)ja (Variable, Parameter, Liste)
Attributeja, auch Instanzattribute mit Werten je Objektnur Konstanten
Konstruktorja (wird von Unterklassen mit super aufgerufen)nein
Methodenabstrakte und fertig implementiertenur Signaturen (abstrakt)
Anzahl je Klassehöchstens eine Oberklasse (extends)beliebig viele (implements)
UMLName kursiv / {abstract}; Vererbung: Linie mit hohlem Dreieck«interface»; Realisierung: gestrichelt mit hohlem Dreieck
MerkeGemeinsamer Zustand und Code einer Familie → abstrakte Klasse. Gemeinsame Fähigkeit ohne gemeinsamen Zustand, oder die Klassen erben schon von einer anderen Klasse → Interface. Beides lässt sich kombinieren: eine abstrakte Oberklasse für die Familie, zusätzlich Interfaces für Fähigkeiten, die nur einige Unterklassen haben.

Keine Mehrfachvererbung von Klassen

In Java und C# kann eine Klasse nur von einer Klasse erben. Grund ist das Diamond-Problem: Erben B und C von A und überschreiben beide die Methode m(), wäre bei einer Klasse D extends B, C unklar, welches m() gilt und ob die Attribute von A einmal oder doppelt vorhanden sind. Interfaces haben dieses Problem nicht, weil sie weder Zustand noch (verpflichtende) Implementierung mitbringen: Die implementierende Klasse schreibt die Methode selbst, es gibt also nur eine Implementierung. Deshalb gilt: eine Oberklasse, beliebig viele Interfaces. (C++ erlaubt Mehrfachvererbung und muss den Konflikt mit zusätzlichen Regeln lösen.)

A+ m()B+ m()C+ m()Dm() = ?in Java nicht erlaubt:class D extends B, C
Abb. A-5: Diamond-Problem – welches m() erbt D?

Redundante Fallunterscheidungen durch Vererbung ersetzen

Ein klassischer Prüfungsfall (S23): Eine Klasse hat ein Attribut typ und mehrere Methoden, die alle mit derselben Fallunterscheidung (wenn typ = … dann …) arbeiten. Jeder neue Typ zwingt dazu, alle diese Methoden zu ändern – vergisst man eine, entsteht ein Fehler. Beispiel aus einer Personalverwaltung:

Mitarbeiter- name : string- typ : string- monatsgehalt : double- stundenlohn : double- stunden : double- lehrjahr : integer+ Mitarbeiter(name : string, typ : string)+ gehalt() : double+ urlaubstage() : integer
Abb. A-6: Vorher – eine Klasse für alle Typen; jedes Objekt schleppt Attribute mit, die nur ein Typ braucht
Methodetyp = "ANGESTELLT"typ = "AUSHILFE"typ = "AZUBI"
gehalt()monatsgehaltstundenlohn * stunden1000 + 100 * lehrjahr
urlaubstage()30028
Mitarbeiter{abstract}- name : string+ Mitarbeiter(name : string)+ getName() : string+ gehalt() : double {abstract}+ urlaubstage() : integer {abstract}Angestellter- monatsgehalt : double+ Angestellter(name : string, monatsgehalt : double)+ gehalt() : double+ urlaubstage() : integerAushilfe- stundenlohn : double- stunden : double+ Aushilfe(name : string, stundenlohn : double)+ setStunden(stunden : double) : void+ gehalt() : double+ urlaubstage() : integerAzubi- lehrjahr : integer+ Azubi(name : string, lehrjahr : integer)+ gehalt() : double+ urlaubstage() : integer
Abb. A-7: Nachher – abstrakte Oberklasse, je Typ eine Unterklasse, keine Fallunterscheidung mehr

Jede Unterklasse implementiert nur „ihre Spalte“ der Tabelle. Beispiel Aushilfe:

klasse Aushilfe erbt von Mitarbeiter
  - stundenlohn : double
  - stunden : double

  konstruktor Aushilfe(name : string,
                       stundenlohn : double)
    super(name)
    this.stundenlohn := stundenlohn
    stunden := 0.0
  Ende konstruktor

  methode setStunden(stunden : double) : void
    this.stunden := stunden
  Ende methode

  methode gehalt() : double
    rückgabe stundenlohn * stunden
  Ende methode

  methode urlaubstage() : integer
    rückgabe 0
  Ende methode
Ende klasse

In Java entspricht erbt von dem Schlüsselwort extends, die überschreibenden Methoden werden mit @Override markiert.

So gehst du vor

  1. Jeder Wert des Typ-Attributs wird eine Unterklasse (Name aus dem Fachbegriff: "AZUBI" → Azubi, "ANGESTELLT" → Angestellter).
  2. Die Oberklasse wird abstrakt, das Attribut typ entfällt (auch aus dem Konstruktor).
  3. Was für alle Typen gleich ist (Attribute, Getter, gleiche Methoden), bleibt nur in der Oberklasse – nicht in den Unterklassen wiederholen.
  4. Jede Methode mit Fallunterscheidung wird in der Oberklasse abstrakt (kursiv bzw. {abstract}) und in jeder Unterklasse mit genau ihrem Zweig implementiert.
  5. Nur für einen Typ benötigte Attribute (lehrjahr, stundenlohn) wandern in die passende Unterklasse.
  6. Haben nur einige Typen eine Fähigkeit, wird sie als Interface modelliert, das nur diese Unterklassen realisieren.
  7. Wer entscheidet beim Erzeugen, welche Unterklasse gebraucht wird? → Fabrikmethode (OOP-04).
  8. Pfeile prüfen: Dreieck immer an der Oberklasse bzw. am Interface.

Vorteil des neuen Entwurfs: Ein neuer Typ ist eine neue Unterklasse; bestehende, getestete Klassen bleiben unverändert (Open-Closed-Prinzip: offen für Erweiterung, geschlossen für Änderung). Keine vergessenen Zweige, kürzere und verständlichere Methoden.

Typische Fehler
So wird es in der Prüfung gefragt

OOP-03 · Polymorphie, Überladen/Überschreiben, Generics Oft

Vorkommen: Teil von „OOP-Konzepte“ (5/9) · Polymorphie: S23 T1 A2ba (4 P: Begriff erläutern), S26 T2 A2bb (4 P: Mechanismus nennen und erklären) · überschreibende Methoden: S23 T1 A2bd · Generics: W22 T2 A1c (3 P: generische Klasse erläutern), W22 T2 A1d (7 P: Sortieren einer List<T>) · Vergleichsfunktion als Parameter: W22 T2 A1b, S24 T2 A2a

Polymorphie (Vielgestaltigkeit)

Definition (prüfungsreif)Polymorphie bedeutet, dass eine Variable vom Typ einer Oberklasse oder eines Interfaces auf Objekte verschiedener Unterklassen verweisen kann und derselbe Methodenaufruf je nach tatsächlichem Objekt unterschiedliches Verhalten auslöst. Möglich wird das durch das Überschreiben von Methoden: Welche Implementierung ausgeführt wird, entscheidet sich erst zur Laufzeit anhand des Objekts (dynamische oder späte Bindung). Der aufrufende Code muss die konkreten Unterklassen nicht kennen und braucht keine Fallunterscheidungen.

Beispiel Stadtbibliothek: Die abstrakte Klasse Medium hat die abstrakte Methode leihfrist() : integer. Buch liefert 28 Tage, DVD 7 Tage, Zeitschrift 14 Tage. Eine Methode, die alle Medien einer Ausleihe durchläuft, ruft einfach m.leihfrist() auf – jedes Objekt antwortet mit seiner eigenen Implementierung:

methode laengsteFrist(medien : List<Medium>)
        : integer
  max : integer := 0
  zähle i von 0 bis medien.size() - 1
    m : Medium := medien.get(i)
    frist : integer := m.leihfrist()
    // welches leihfrist()? Das Objekt
    // entscheidet (dynamische Bindung)
    wenn frist > max dann
      max := frist
    Ende wenn
  Ende zähle
  rückgabe max
Ende methode
medien : List<Medium>[0][1][2]: Buchleihfrist() → 28: DVDleihfrist() → 7: Zeitschriftleihfrist() → 14Aufruf überall gleich: medien.get(i).leihfrist()
Abb. A-8: Eine Liste vom Typ Medium, drei verschiedene Objekte, drei verschiedene Implementierungen

Kommt später eine neue Medienart hinzu (z. B. Hoerbuch mit 21 Tagen), wird nur eine neue Unterklasse geschrieben – die Methode laengsteFrist bleibt unverändert.

FormMechanismusEntscheidung
dynamische Polymorphie (meist gemeint)Überschreiben von Methoden in Unterklassenzur Laufzeit (dynamische/späte Bindung)
statische PolymorphieÜberladen (gleicher Name, andere Parameterliste)beim Übersetzen (statische/frühe Bindung)
parametrische PolymorphieGenerics (List<T>)beim Übersetzen

Statischer und dynamischer Typ

Bei Medium m := new DVD("Metropolis") ist Medium der statische Typ (deklarierter Typ der Variablen) und DVD der dynamische Typ (Klasse des Objekts, auf das m gerade zeigt).

Merke: zwei Fragen, zwei Typen
  1. Ist der Aufruf erlaubt? – entscheidet der Compiler mit dem statischen Typ. Über m sind nur Methoden aufrufbar, die Medium kennt. Eine Methode, die nur DVD hat (z. B. laufzeitMinuten()), ist über m nicht aufrufbar.
  2. Welche überschriebene Implementierung läuft? – entscheidet die Laufzeitumgebung mit dem dynamischen Typ: m.leihfrist() führt DVD.leihfrist() aus.
  3. Ruft eine geerbte Methode intern eine überschriebene Methode auf (this.leihfrist()), gilt ebenfalls der dynamische Typ des Objekts.

Überschreiben und Überladen

Überschreiben (Overriding)Überladen (Overloading)
Wo?Oberklasse und Unterklasse (bzw. Interface und implementierende Klasse)meist in derselben Klasse; auch eine Unterklasse kann eine geerbte Methode überladen
Signaturgleicher Name, gleiche Parameterliste, kompatibler Rückgabetypgleicher Name, andere Parameterliste (Anzahl, Typen, Reihenfolge)
Wirkungersetzt die geerbte Implementierung (Zugriff auf das Original mit super.methode())zusätzliche Variante; die anderen bleiben bestehen
Bindungdynamisch zur Laufzeit (dynamischer Typ des Objekts)statisch beim Übersetzen (statische Typen der Argumente)
JavaAnnotation @Override (Compiler prüft, ob wirklich überschrieben wird)keine Annotation
BeispielDVD.leihfrist() ersetzt Medium.leihfrist()suche(titel : string) und suche(isbn : string, auflage : integer)
Falle: Überladen wird nach dem statischen Typ ausgewählt

Eine Klasse Etikettendrucker hat zwei überladene Methoden drucke(m : Medium) (druckt „Medium“) und drucke(b : Buch) (druckt „Buch“).

Medium m = new Buch("Faust");
Buch b = new Buch("Woyzeck");
drucker.drucke(m);   // Ausgabe: Medium   (statischer Typ von m ist Medium!)
drucker.drucke(b);   // Ausgabe: Buch

Obwohl m auf ein Buch zeigt, wird drucke(Medium) gewählt, denn welche überladene Variante passt, entscheidet der Compiler mit den statischen Typen. Dynamisch gebunden wird nur beim Überschreiben.

Falle: Unterklasse überlädt statt zu überschreibenSchreibt eine Unterklasse eine Methode mit gleichem Namen, aber anderer Parameterliste (leihfrist(verlaengert : boolean)), ist das kein Überschreiben. Die geerbte leihfrist() bleibt unverändert und wird weiterhin aufgerufen. @Override würde diesen Fehler beim Übersetzen melden.

Upcast, Downcast und instanceof

Generics: generische Klassen und Methoden

Eine generische Klasse hat einen oder mehrere Typparameter (Platzhalter für einen Datentyp), z. B. class Warteschlange<T>. Erst beim Verwenden wird der konkrete Typ festgelegt: Warteschlange<Patient>, Warteschlange<String>. Die bekanntesten generischen Klassen sind die Collections: List<T>, Map<K, V>.

Pseudocode

klasse Warteschlange<T>
  - elemente : List<T>

  konstruktor Warteschlange()
    elemente := new List<T>()
  Ende konstruktor

  methode einreihen(e : T) : void
    elemente.add(e)
  Ende methode

  methode naechster() : T
    wenn elemente.size() = 0 dann
      rückgabe null
    Ende wenn
    erster : T := elemente.get(0)
    elemente.remove(erster)
    rückgabe erster
  Ende methode
Ende klasse
Warteschlange- elemente : List<T>+ Warteschlange()+ einreihen(e : T) : void+ naechster() : TT
Abb. A-9: UML-Notation einer generischen Klasse – der Typparameter steht im gestrichelten Kästchen

Verwendung (Java)

Warteschlange<Patient> praxis = new Warteschlange<>();
praxis.einreihen(new Patient("Ahmadi"));
praxis.einreihen(new Patient("Brandt"));
Patient naechster = praxis.naechster();   // kein Cast nötig
// praxis.einreihen("Brandt");            // Compilerfehler: String ist kein Patient

Vorteile gegenüber einer Klasse, die Elemente vom Typ Object speichert:

Weitere Formen: generische Methode mit eigenem Typparameter (<T> T letztes(List<T> liste)), beschränkter Typparameter <T extends Medium> – dann dürfen auf T die Methoden von Medium aufgerufen werden. Typparameter stehen nur für Referenztypen: List<Integer>, nicht List<int>.

Funktionsobjekte: eine Vergleichsfunktion als Parameter

Ein generischer Algorithmus (Sortieren, Minimum, „ist die Liste sortiert?“) weiß nicht, wie zwei Objekte vom Typ T zu vergleichen sind. Deshalb bekommt er ein Objekt mit einer Vergleichsmethode übergeben – in Java das Interface Comparator<T> mit compare(a, b):

// true, wenn die Liste nach dem Kriterium v
// aufsteigend sortiert ist
methode istAufsteigend<T>(liste : List<T>,
                          v : Comparator<T>) : boolean
  zähle i von 1 bis liste.size() - 1
    wenn v.compare(liste.get(i - 1), liste.get(i)) > 0 dann
      rückgabe false
    Ende wenn
  Ende zähle
  rückgabe true
Ende methode

Verwendung in Java (Vergleichsfunktion als Lambda-Ausdruck)

List<Buch> buecher = new ArrayList<>();
buecher.add(new Buch("Faust", 1808));
buecher.add(new Buch("Woyzeck", 1879));
Comparator<Buch> nachJahr = (x, y) -> x.getJahr() - y.getJahr();
boolean ok = istAufsteigend(buecher, nachJahr);   // true

Das Sortieren mit Vergleichsfunktion (Bubblesort) ist in Block 02 (ALG-05) ausführlich behandelt.

Typische Fehler
So wird es in der Prüfung gefragt

OOP-04 · Entwurfsmuster: Grundlagen, Kategorien, Erzeugungsmuster Wahrscheinlich

Vorkommen: Teil von „Entwurfsmuster“ (3/9: S22, S23, W24) · W24 T1 A4ba (4 P: zwei Vorteile von Entwurfsmustern), W24 T1 A4bb (2 P: Beispiele für Erzeugungs- und Verhaltensmuster) · S23 T1 A2bb/bc (statische Fabrikmethode im Klassendiagramm modellieren und in Pseudocode implementieren)

Was ist ein Entwurfsmuster?

Ein Entwurfsmuster (Design Pattern) ist eine bewährte, wiederverwendbare Lösungsvorlage für ein wiederkehrendes Entwurfsproblem der Softwareentwicklung. Es ist kein fertiger Code und keine Bibliothek, sondern eine Idee mit typischer Struktur (beteiligte Klassen, Interfaces und ihre Beziehungen), die auf die konkrete Situation übertragen wird. Bekannt wurde das Konzept durch den Katalog der „Gang of Four“ (Gamma, Helm, Johnson, Vlissides, 1994) mit 23 Mustern. Jedes Muster wird beschrieben durch Name, Problem, Lösung und Konsequenzen (Vor- und Nachteile).

VorteilErläuterung
Bewährte LösungenDie Lösung wurde vielfach erprobt; typische Entwurfsfehler werden vermieden, die Qualität steigt.
Gemeinsames Vokabular„Hier nehmen wir einen Observer“ ersetzt eine lange Beschreibung – das erleichtert Kommunikation im Team und Dokumentation.
Wartbarkeit und ErweiterbarkeitMuster entkoppeln Klassen (z. B. kennt das Subjekt nur ein Interface). Erweiterungen sind möglich, ohne bestehenden Code zu ändern.
Wiederverwendbarkeit, ZeitersparnisDer Entwurf muss nicht neu erfunden werden; Lösungen lassen sich auf andere Projekte übertragen.
VerständlichkeitWer das Muster kennt, versteht fremden Code schneller; neue Teammitglieder arbeiten sich leichter ein.

Nachteile: zusätzliche Klassen und Indirektion (mehr Komplexität), Gefahr des „Overengineering“ (Muster ohne echtes Problem), Einarbeitungsaufwand, falsch gewählte Muster erschweren die Wartung.

Die drei Kategorien

KategorieLeitfrageBeispiele
ErzeugungsmusterWie werden Objekte erzeugt? Die Erzeugung wird vom Rest des Programms getrennt.Singleton, Fabrikmethode, Abstrakte Fabrik, Builder (Erbauer), Prototyp
StrukturmusterWie werden Klassen und Objekte zu größeren Strukturen zusammengesetzt?Adapter, Fassade, Decorator (Dekorierer), Kompositum, Proxy (Stellvertreter), Brücke, Fliegengewicht
VerhaltensmusterWie arbeiten Objekte zusammen, wie werden Aufgaben und Algorithmen verteilt?Observer (Beobachter), Strategie, Schablonenmethode, Befehl (Command), Iterator, Zustand (State), Besucher, Zuständigkeitskette, Vermittler, Memento
MerkeErzeugen – Zusammensetzen – Zusammenarbeiten. Für die Prüfung reicht es, je Kategorie zwei Muster sicher zu kennen: Singleton und Fabrikmethode (Erzeugung), Adapter und Fassade (Struktur), Observer und Strategie (Verhalten). MVC (Model-View-Controller) ist kein GoF-Muster dieser drei Kategorien, sondern ein Architekturmuster, das intern Observer und Strategie verwendet.

Singleton

Zweck: Von einer Klasse darf es höchstens ein Objekt geben, und alle Programmteile sollen über einen globalen Zugriffspunkt genau dieses Objekt erhalten. Typische Einsätze: zentrales Protokoll (Logger), Konfiguration, Verbindungspool, Druckwarteschlange.

Aufbau (drei Bausteine):

  1. ein privates statisches Attribut, das die einzige Instanz speichert (anfangs null),
  2. ein privater Konstruktor – von außen ist kein new möglich,
  3. eine öffentliche statische Methode getInstance(), die beim ersten Aufruf das Objekt erzeugt („lazy“) und danach immer dasselbe Objekt zurückgibt.
Protokoll- instanz : Protokoll- Protokoll()+ getInstance() : Protokoll+ schreibe(text : string) : void
Abb. A-10: Singleton Protokoll – statische Elemente unterstrichen
class Protokoll {
    private static Protokoll instanz = null;

    private Protokoll() {
        // z. B. Protokolldatei öffnen
    }

    public static synchronized Protokoll getInstance() {
        if (instanz == null) {
            instanz = new Protokoll();
        }
        return instanz;
    }

    public void schreibe(String text) {
        System.out.println("[LOG] " + text);
    }
}

Aufruf überall im Programm: Protokoll.getInstance().schreibe("Start"). Protokoll.getInstance() == Protokoll.getInstance() liefert true.

Thread-Sicherheit: Rufen zwei Threads getInstance() gleichzeitig zum ersten Mal auf, können beide instanz = null sehen und je ein Objekt erzeugen – dann gibt es zwei. Abhilfe: die Methode synchronized deklarieren (nur ein Thread gleichzeitig), das Objekt schon beim Laden der Klasse erzeugen (private static final Protokoll INSTANZ = new Protokoll();, „eager“) oder in Java ein enum mit genau einem Wert verwenden.

Nachteile: globaler Zustand und versteckte Abhängigkeiten (man sieht einer Methode nicht an, dass sie das Singleton benutzt), schlecht testbar (das Objekt lässt sich nicht durch eine Testattrappe ersetzen), Probleme bei Nebenläufigkeit, die Klasse kümmert sich zusätzlich um ihre eigene Erzeugung.

Fabrikmethode (Factory Method) und statische Fabrikmethode

Problem: An vielen Stellen im Code steht eine Fallunterscheidung, die je nach Parameter eine andere konkrete Klasse mit new erzeugt. Kommt eine Klasse hinzu, müssen alle diese Stellen geändert werden, und der aufrufende Code hängt von allen konkreten Klassen ab.

Lösung (statische Fabrikmethode, wie in S23): Eine einzige – meist statische – Methode in der Oberklasse oder in einer eigenen Fabrikklasse entscheidet anhand eines Parameters, welches Objekt erzeugt wird, und gibt es mit dem Typ der Oberklasse bzw. des Interfaces zurück. Der aufrufende Code kennt nur noch die Abstraktion.

Beispiel: Berichte sollen als CSV, JSON oder XML exportiert werden.

Berichtsmodul+ erstelleBericht(format : string) : voidExporter{abstract}+ exportiere(text : string) : void {abstract}+ erzeugeExporter(format : string) : Exporter {static}«use»CsvExporter+ exportiere(text : string) : voidJsonExporter+ exportiere(text : string) : voidXmlExporter+ exportiere(text : string) : void
Abb. A-11: Statische Fabrikmethode erzeugeExporter in der abstrakten Oberklasse (statisch = unterstrichen, hier zusätzlich {static})
methode erzeugeExporter(format : string) : Exporter  {static}
  wenn format = "CSV" dann
    rückgabe new CsvExporter()
  sonst wenn format = "JSON" dann
    rückgabe new JsonExporter()
  sonst wenn format = "XML" dann
    rückgabe new XmlExporter()
  Ende wenn
  rückgabe null      // unbekanntes Format
Ende methode

// Verwendung – keine Fallunterscheidung mehr:
e : Exporter := Exporter.erzeugeExporter(wahl)
e.exportiere(bericht)

Vorteile: Die Erzeugungslogik steht an einer Stelle (eine neue Klasse = eine Änderung in der Fabrik), der aufrufende Code ist unabhängig von den konkreten Klassen (lose Kopplung), redundante Fallunterscheidungen verschwinden, die Fabrik kann Parameter prüfen und Standardwerte setzen.

Fabrikmethode im Sinne der Gang of Four: Statt einer statischen Methode deklariert eine abstrakte „Erzeuger“-Klasse eine abstrakte Methode erzeugeProdukt(); jede Unterklasse des Erzeugers überschreibt sie und entscheidet so, welches konkrete Produkt entsteht. Die Abstrakte Fabrik erweitert die Idee auf ganze Familien zusammengehöriger Objekte (z. B. alle Bedienelemente im hellen oder dunklen Design). In der Prüfung genügt meist die statische Variante.

Builder (Erbauer) – kurz

Problem: Ein Objekt hat viele, teils optionale Angaben. Konstruktoren mit vielen Parametern sind unlesbar (new Reise("Rom", null, true, false, 2, null)) oder man braucht unzählige überladene Konstruktoren. Lösung: Eine eigene Builder-Klasse sammelt die Angaben Schritt für Schritt über sprechende Methoden und erzeugt mit build() das fertige, vollständig geprüfte Objekt.

Reise r = new Reise.Builder("Rom")      // Pflichtangabe
        .hotel("Albergo Sole")          // optionale Angaben
        .personen(2)
        .build();                       // prüft und erzeugt das Objekt
Typische Fehler
So wird es in der Prüfung gefragt

OOP-05 · Verhaltens- und Strukturmuster: Observer, Strategie, MVC, Adapter, Fassade, Decorator Wahrscheinlich

Vorkommen: Teil von „Entwurfsmuster“ (3/9) · Observer: S22 T1 A3 (durchgängig: Beziehung im Klassendiagramm, Interface-Methoden, Realisierung erläutern, Sequenzdiagramm, notifyObservers in Pseudocode – 23 der 30 P), W24 T1 A4bc (4 P: geeignetes Muster für die Aktualisierung aller Anzeigen nennen und begründen)

Observer (Beobachter)

Problem: Ein Objekt ändert seinen Zustand, und beliebig viele andere Objekte sollen darüber automatisch informiert werden – ohne dass das geänderte Objekt deren konkrete Klassen kennen muss (1:n-Abhängigkeit, lose Kopplung). Signalwörter in der Aufgabe: „sobald sich … ändert, sollen alle Anzeigen aktualisiert werden“, „weitere Anzeigen sollen später hinzukommen“, „App, Display und Verwaltung zeigen denselben Wert“.

RolleAufgabeMethoden (englisch / deutsch)
Subject (Interface oder abstrakte Klasse)legt fest, wie man sich an- und abmeldet und wie benachrichtigt wirdaddObserver(o) / anmelden, removeObserver(o) / abmelden, notifyObservers() / benachrichtigen
konkretes Subjecthat den beobachteten Zustand und eine Liste der angemeldeten Observer; ruft nach jeder Änderung notifyObservers() aufzusätzlich fachliche Setter/Getter
Observer (Interface)Methode, die bei einer Änderung aufgerufen wirdupdate() / aktualisieren
konkrete Observerimplementieren update(), z. B. Anzeige neu zeichnen–

Beispiel Kühlhaus: Ein Temperatursensor misst die Temperatur. Bei jeder neuen Messung sollen eine Anzeigetafel und eine Alarmanlage informiert werden; später kommt ein Protokoll dazu.

«interface»Subject+ addObserver(o : Observer) : void+ removeObserver(o : Observer) : void+ notifyObservers() : voidTemperatursensor- temperatur : double- observers : List<Observer>+ setTemperatur(t : double) : void+ getTemperatur() : double+ addObserver(o : Observer) : void+ removeObserver(o : Observer) : void+ notifyObservers() : void«interface»Observer+ update(temperatur : double) : voidAnzeigetafel+ update(temperatur : double) : voidAlarmanlage- grenzwert : double+ Alarmanlage(grenzwert : double)+ update(temperatur : double) : voidobservers*1
Abb. A-12: Observer-Muster (Push-Variante): Das Subjekt kennt seine Beobachter nur über das Interface Observer

Pseudocode (Methoden des Temperatursensors)

methode addObserver(o : Observer) : void
  observers.add(o)
Ende methode

methode removeObserver(o : Observer) : void
  observers.remove(o)
Ende methode

methode notifyObservers() : void
  zähle i von 0 bis observers.size() - 1
    observers.get(i).update(temperatur)
  Ende zähle
Ende methode

methode setTemperatur(t : double) : void
  temperatur := t
  notifyObservers()      // nach jeder Änderung!
Ende methode

Der zeitliche Ablauf: Zuerst melden sich die Beobachter an. Bei einer neuen Messung ruft der Sensor sich selbst notifyObservers() auf; diese Methode ruft nacheinander update(…) bei jedem angemeldeten Objekt auf. Erst wenn alle fertig sind, kehrt der Aufruf zurück.

:Steuerungsensor : Temperatursensortafel : Anzeigetafelalarm : AlarmanlageaddObserver(tafel)addObserver(alarm)setTemperatur(9.0)notifyObservers()update(9.0)update(9.0)ausloesen()(9.0 > Grenzwert 8.0)
Abb. A-13: Sequenzdiagramm zum Observer-Ablauf (Push): Anmelden, Zustandsänderung, Benachrichtigung aller Beobachter

Push oder Pull? Es gibt zwei Varianten, wie die Beobachter an die neuen Daten kommen:

Push-VariantePull-Variante
Signaturupdate(temperatur : double) – Daten als Parameterupdate() (ohne Daten)
DatenflussDas Subjekt schickt die geänderten Daten mit.Der Beobachter holt sich die Daten selbst über Getter beim Subjekt (quelle.getTemperatur()).
Referenz auf das Subjektnicht nötignötig (Attribut im Beobachter, meist im Konstruktor übergeben – dort meldet sich der Beobachter oft auch gleich an)
Vorteileinfach, kein Rückruf nötig, weniger Aufrufejeder Beobachter holt nur, was er braucht; Interface bleibt gleich, wenn neue Daten dazukommen
Nachteilalle bekommen alle Daten; ändern sich die Daten, ändert sich das Interfacezusätzliche Aufrufe; Beobachter ist an die konkrete Subjektklasse gebunden

Vorteile des Observer-Musters: lose Kopplung (das Subjekt kennt nur das Interface), beliebig viele Beobachter zur Laufzeit an- und abmeldbar, neue Beobachter-Klassen ohne Änderung des Subjekts (Erweiterbarkeit), kein ständiges Abfragen (Polling). Nachteile: Reihenfolge der Benachrichtigung nicht festgelegt, Kaskaden von Aktualisierungen, vergessenes Abmelden hält Objekte im Speicher, Ablauf schwerer nachvollziehbar.

Muster wählen und begründen – so formulierst du (Beispiel Fußball-Liveticker) „Geeignet ist das Observer-Muster. Der Spielstand ist das Subjekt; die App, die Stadionanzeige und der Liveticker sind Beobachter, die sich beim Spielstand anmelden. Ändert sich der Spielstand, ruft das Subjekt notifyObservers() auf, das bei allen angemeldeten Beobachtern update() aufruft. Dadurch werden alle Anzeigen automatisch und gleichzeitig aktualisiert. Weil das Subjekt nur das Observer-Interface kennt, können später weitere Anzeigen ergänzt werden, ohne die Klasse Spielstand zu ändern (lose Kopplung).“
Aufbau: Muster nennen → Rollen zuordnen → Ablauf in einem Satz → Vorteil für genau diese Situation.

Strategie (Strategy)

Problem: Für eine Aufgabe gibt es mehrere Verfahren (Algorithmen), die austauschbar sein sollen – auch zur Laufzeit – ohne dass die nutzende Klasse lange wenn … sonst wenn …-Ketten enthält. Lösung: Jedes Verfahren wird in eine eigene Klasse gekapselt, die ein gemeinsames Interface realisiert. Die nutzende Klasse (Kontext) hat ein Attribut vom Typ des Interfaces und delegiert die Arbeit an das gerade eingesetzte Strategie-Objekt.

Navigation- strategie : Routenstrategie+ Navigation(s : Routenstrategie)+ setStrategie(s : Routenstrategie) : void+ planeRoute(start : string, ziel : string) : Route«interface»Routenstrategie+ berechneRoute(start : string, ziel : string) : Routestrategie1SchnellsteRouteKuerzesteRouteFahrradrouteJede Strategie-Klasse implementiertberechneRoute(start, ziel) mitihrem eigenen Verfahren.
Abb. A-14: Strategie-Muster am Beispiel einer Navigations-App
// in der Klasse Navigation (Kontext)
methode setStrategie(s : Routenstrategie) : void
  strategie := s
Ende methode

methode planeRoute(start : string, ziel : string) : Route
  rückgabe strategie.berechneRoute(start, ziel)
Ende methode
Navigation nav = new Navigation(new SchnellsteRoute());
Route r1 = nav.planeRoute("Kiel", "Lübeck");
nav.setStrategie(new Fahrradroute());   // Wechsel zur Laufzeit
Route r2 = nav.planeRoute("Kiel", "Lübeck");

Strategie statt Unterklassen des Kontexts: Das Verfahren kann zur Laufzeit gewechselt werden; es entstehen keine Unterklassen für jede Kombination (Navigation für Auto mit schnellster Route, Navigation für Fahrrad …); jedes Verfahren ist einzeln test- und wiederverwendbar; neue Verfahren ändern den Kontext nicht. Das Prinzip dahinter: Komposition statt Vererbung. (Ähnlich aufgebaut ist das Zustandsmuster; dort wechselt das Objekt sein Verhalten aber selbst, je nach Zustand.)

MVC (Model-View-Controller)

Architekturmuster für Anwendungen mit Benutzeroberfläche. Es trennt die Anwendung in drei Teile mit klaren Zuständigkeiten:

ModelViewControllerEingabeändertbenachrichtigt(Observer)liest Daten
Abb. A-15: Zusammenspiel in MVC

Vorteile: Trennung der Zuständigkeiten, Oberfläche austauschbar (Web, App) bei gleichem Model, mehrere Ansichten derselben Daten, parallele Entwicklung im Team, Model ohne Oberfläche testbar.

Adapter

Problem: Eine vorhandene Klasse (Fremdbibliothek, Altsystem) bietet die benötigte Funktion, aber mit einer anderen Schnittstelle als erwartet, und sie darf oder kann nicht geändert werden. Lösung: Eine Adapter-Klasse realisiert die erwartete Schnittstelle und leitet jeden Aufruf – übersetzt – an das vorhandene Objekt weiter. Beispiel: Die Kühlhaus-Software erwartet Temperaturquelle.getCelsius(), ein neu gekaufter Sensor liefert über seine Bibliothek nur readFahrenheit().

Kuehlhaussteuerung+ regeln() : void«interface»Temperaturquelle+ getCelsius() : doubleFahrenheitAdapter- sensor : FahrenheitSensor+ getCelsius() : double«Fremdbibliothek»FahrenheitSensor+ readFahrenheit() : doublesensor1getCelsius():f := sensor.readFahrenheit()rückgabe (f - 32) * 5 / 9
Abb. A-16: Objektadapter – FahrenheitAdapter übersetzt zwischen erwarteter und vorhandener Schnittstelle

Fassade (Facade)

Problem: Ein Teilsystem besteht aus vielen Klassen, die in einer bestimmten Reihenfolge benutzt werden müssen. Jeder Nutzer müsste alle kennen. Lösung: Eine Fassadenklasse bietet eine einfache, vereinheitlichte Schnittstelle und ruft intern die Klassen des Teilsystems auf. Beispiel: Ein Reiseportal ruft nur ReiseFassade.bucheReise(kunde, angebot) auf; die Fassade reserviert Flug und Hotel, zieht die Zahlung ein und verschickt die Bestätigung.

Reiseportal+ buchen() : voidReiseFassade+ bucheReise(kunde : Kunde, angebot : Angebot) : booleanTeilsystem BuchungFlugServiceHotelServiceZahlungsdienstMailService
Abb. A-17: Fassade vor einem Teilsystem

Abgrenzung: Ein Adapter macht eine vorhandene Schnittstelle passend zu einer erwarteten. Eine Fassade vereinfacht viele Schnittstellen zu einer neuen, einfacheren.

Decorator (Dekorierer)

Problem: Objekte sollen zur Laufzeit beliebig kombinierbare Zusatzfunktionen erhalten. Eine Unterklasse für jede Kombination würde zur „Klassenexplosion“ führen. Lösung: Ein Decorator realisiert dieselbe Schnittstelle wie das Objekt, das er „umhüllt“, hält eine Referenz darauf, leitet Aufrufe weiter und ergänzt eigenes Verhalten. Decorator lassen sich beliebig verschachteln. Beispiel Café: new Sirup(new Milch(new Kaffee())) – preis() ergibt 2,50 + 0,30 + 0,50 = 3,30 €. Bekanntes Java-Beispiel: new BufferedReader(new FileReader("daten.txt")).

«interface»Getraenk+ preis() : doubleKaffee+ preis() : doubleZutat{abstract}# getraenk : Getraenk+ Zutat(g : Getraenk)Milch+ preis() : doubleSirup+ preis() : doublegetraenk1
Abb. A-18: Decorator – Zutat ist ein Getraenk und enthält ein Getraenk

Welches Muster passt? Signalwörter

Situation in der AufgabeMusterKategorie
„Es darf nur ein Objekt geben“, zentrale Konfiguration, globaler ZugriffSingletonErzeugung
Je nach Typ oder Parameter das passende Objekt erzeugen; neue Typen sollen leicht hinzukommenFabrikmethodeErzeugung
Objekt mit vielen optionalen Angaben schrittweise aufbauenBuilderErzeugung
Vorhandene oder fremde Klasse passt nicht zur erwarteten SchnittstelleAdapterStruktur
Komplexes Teilsystem über eine einfache Schnittstelle nutzenFassadeStruktur
Zusatzfunktionen dynamisch kombinieren, ohne Unterklassen für jede KombinationDecoratorStruktur
Bei einer Änderung sollen alle abhängigen Anzeigen/Objekte automatisch aktualisiert werdenObserverVerhalten
Berechnungsverfahren austauschbar, zur Laufzeit wählbarStrategieVerhalten
Daten, Darstellung und Steuerung einer Oberfläche trennenMVCArchitekturmuster
Typische Fehler
So wird es in der Prüfung gefragt

B · Belegsatz

Enthält nur, was dir auch die IHK beilegen würde: die Notationshilfen (UML-Klassendiagramm und UML-Sequenzdiagramm wie in den Belegsätzen S22/S24, Pseudocode-Konventionen wie S24) und die Anlagen zur Ausgangssituation. Keine Erklärungen.

UML-Klassendiagramm

UML-KlassendiagrammKlasseKlasseattribut1attribut2methode1()methode2()Klasse{abstract}abstrakteMethode1()methode2()Notiz«interface»Schnittstellemethode1()methode2()Syntax für Attribute:Sichtbarkeit Attributname : Typ {Eigenschaften}Syntax für Methoden:Sichtbarkeit Methodenname(parameter1 : Typ, …) : Rückgabetyp {Eigenschaften}Sichtbarkeit: + public # protected - private ~ packageEigenschaften: {static, final, …}Klasse1Klasse2VererbungKlasse1Klasse2AssoziationKlasse1Klasse2Multiplizität/Kardinalität1*Klasse1Klasse2gerichtete AssoziationGanzesTeilAggregationGanzesExistenz-abhängiges TeilKompositionKlasse«interface»SchnittstelleImplementierung

UML-Sequenzdiagramm

UML-Sequenzdiagrammx:Klasse1y:Klasse2LebenslinieNachricht()Ausführung(opt.)alt[i>0][else]nachricht1()nachricht2()17Selbstaufrufcreate:KlasseXObjekt-DestruktionObjekt-Konstruktionsynchrone NachrichtAntwortasynchrone Nachrichtsynchrone Nachricht:Der Sender wartet, bis der Empfängerden Aufruf beendet hat; ein Rückgabe-wert ist möglich (z. B. 17).asynchrone Nachricht:Der Sender wartet nicht auf eineAntwort des Empfängers.

Pseudocode, prozedurale Programmierung

PseudocodeBeschreibung
wenn [ Bedingung ] dann
   [ Anweisung ]
sonst
   [ Anweisung ]
Ende wenn
Verzweigung
zähle [ Variable ] von [ Startwert ] bis [ Endwert ]
   [ Schleifeninhalt ]
Ende zähle
Zählschleife (Iteration)
solange [ Bedingung ]
   [ Schleifeninhalt ]
Ende solange
Kopfgesteuerte Schleife
wiederhole
   [ Schleifeninhalt ]
solange [ Bedingung ]
Fußgesteuerte Schleife
:=
Zuweisung
=; <; >; <=; >=; <>
Vergleiche
integer
double
string
boolean
array
Datentypen

Anlage 1 · Ausgangssituation

Die Aufgaben dieses Blocks beziehen sich auf die folgende Ausgangssituation:

Die Kranich Logistik GmbH mit Sitz in Lindhafen ist ein regionaler Paket- und Kurierdienst in Norddeutschland. Sie stellt Briefe, Pakete und Expresssendungen für Online-Händler, Geschäfts- und Privatkunden zu. Zum Unternehmen gehören ein Sortierzentrum mit drei Hallen, 40 Paketstationen mit Fächern in drei Größen sowie ein Fuhrpark aus Lastenrädern, E-Transportern und Kühltransportern.

Die bisherige Software ist über viele Jahre gewachsen und lässt sich kaum noch erweitern. Ihr Ausbildungsbetrieb, die Lorenz Software GmbH, entwickelt deshalb die objektorientierte Plattform „KraniTrack“ (Programmiersprache Java). Sie arbeiten im Entwicklungsteam in folgenden Teilprojekten mit:

Hinweise: Heute ist der 10.10.2026. Für alle Attribute existieren Get-Methoden, sofern nichts anderes angegeben ist. Geldbeträge in Euro, Gewichte in kg. Zeichenketten dürfen im Pseudocode mit = verglichen werden.

Anlage 2 · Vorhandene Hilfsklassen

Generische Klasse List<T>
MethodeBeschreibung
new List<T>()erzeugt eine leere Liste für Elemente vom Typ T
add(element : T) : voidhängt element am Ende der Liste an
get(index : integer) : Tliefert das Element an der Position index (0 ≤ index < size())
size() : integerliefert die Anzahl der Elemente
contains(element : T) : booleanliefert true, wenn element in der Liste enthalten ist
remove(element : T) : booleanentfernt element aus der Liste
Klasse Datum
MethodeBeschreibung
plusTage(tage : integer) : Datumliefert ein neues Datum, das tage Tage später liegt
istVorOderGleich(d : Datum) : booleanliefert true, wenn das Datum vor d liegt oder gleich d ist
Klasse Sendung (Teilprojekt Paketstationen, Auszug)
MethodeBeschreibung
getSendungsNr() : stringliefert die Sendungsnummer, z. B. "KT-1001"
getGroesse() : stringliefert die Größenklasse der Sendung: "S", "M" oder "L"

Anlage 3 · Quelltext der Klasse Etikett (Java)

Jedes Versandetikett erhält eine fortlaufende Nummer ab 1000. Das Gewicht muss größer als 0 und höchstens 31,5 kg sein.

class Etikett {
    private static int naechsteNr = 1000;
    private int nummer;
    private String empfaenger;
    private double gewicht;

    public Etikett(String empfaenger, double gewicht) {
        this.nummer = naechsteNr;
        naechsteNr = naechsteNr + 1;
        this.empfaenger = empfaenger;
        this.gewicht = 1.0;
        setGewicht(gewicht);
    }

    public Etikett(String empfaenger) {
        this(empfaenger, 1.0);
    }

    public void setGewicht(double gewicht) {
        if (gewicht > 0 && gewicht <= 31.5) {
            this.gewicht = gewicht;
        }
    }

    public int getNummer() {
        return nummer;
    }

    public static int getAnzahl() {
        return naechsteNr - 1000;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Etikett)) {
            return false;
        }
        Etikett e = (Etikett) o;
        return empfaenger.equals(e.empfaenger) && gewicht == e.gewicht;
    }

    @Override
    public int hashCode() {
        return empfaenger.hashCode() + Double.hashCode(gewicht);
    }

    @Override
    public String toString() {
        return nummer + " | " + empfaenger + " | " + gewicht + " kg";
    }
}

Anlage 4 · Anforderungen an die Klasse Paketfach

Anlage 5 · Quelltext Fuhrpark (Java)

abstract class Fahrzeug {
    private String kennung;
    protected int reichweiteKm;

    public Fahrzeug(String kennung, int reichweiteKm) {
        this.kennung = kennung;
        this.reichweiteKm = reichweiteKm;
        System.out.println("Fahrzeug " + kennung + " angelegt");
    }

    public String getKennung() {
        return kennung;
    }

    public abstract double kostenProKm();

    public final String info() {
        return kennung + " (" + reichweiteKm + " km)";
    }
}

interface Kuehlbar {
    boolean kuehlen(int zielTemperatur);
}

class Lastenrad extends Fahrzeug {
    public Lastenrad(String kennung) {
        super(kennung, 60);
        System.out.println("Lastenrad " + getKennung() + " bereit");
    }

    @Override
    public double kostenProKm() {
        return 0.05;
    }
}

class Kuehltransporter extends Fahrzeug implements Kuehlbar {
    private int temperatur;

    public Kuehltransporter(String kennung, int reichweiteKm) {
        super(kennung, reichweiteKm);
        temperatur = 20;
        System.out.println("Kuehlung auf " + temperatur + " Grad");
    }

    @Override
    public double kostenProKm() {
        return 0.32;
    }

    @Override
    public boolean kuehlen(int zielTemperatur) {
        if (zielTemperatur < -20) {
            return false;
        }
        temperatur = zielTemperatur;
        return true;
    }
}

Anlage 6 · Bisherige Klasse Postsendung

In der Sendungsverwaltung gibt es bisher eine einzige Klasse für alle Sendungsarten. Das Attribut typ enthält "BRIEF", "PAKET" oder "EXPRESS".

Postsendung- sendungsNr : string- typ : string- gewicht : double+ Postsendung(sendungsNr : string, typ : string, gewicht : double)+ getSendungsNr() : string+ getGewicht() : double+ berechnePorto() : double+ maxGewicht() : double+ istStationsfaehig() : boolean+ berechneVersicherung(warenwert : double) : double

Alle vier fachlichen Methoden enthalten dieselbe Fallunterscheidung nach typ:

Methode"BRIEF""PAKET""EXPRESS"
berechnePorto()0,95 bis einschließlich 0,05 kg, sonst 1,805,49 bis einschließlich 5 kg, sonst 8,9912,90 + 0,80 je kg Gewicht
maxGewicht()1,031,520,0
istStationsfaehig()falsetruefalse
berechneVersicherung(warenwert)0,0 (Briefe sind nicht versicherbar)1 % des Warenwerts, mindestens 2,000,5 % des Warenwerts

Künftig sollen weitere Sendungsarten hinzukommen, z. B. Sperrgut und Großbrief.

Anlage 7 · Quelltext Zustellarten und Kasse (Java)

class Zustellung {
    public String beschreibung() {
        return "Zustellung";
    }

    public double gebuehr() {
        return 4.00;
    }

    public String quittung() {
        return beschreibung() + ": " + gebuehr() + " EUR";
    }
}

class Expresszustellung extends Zustellung {
    @Override
    public String beschreibung() {
        return "Express";
    }

    @Override
    public double gebuehr() {
        return super.gebuehr() + 6.00;
    }

    public String zeitfenster() {
        return "bis 12 Uhr";
    }
}

class Nachtzustellung extends Expresszustellung {
    @Override
    public String beschreibung() {
        return "Nacht-" + super.beschreibung();
    }
}

class Kasse {
    public String buche(Zustellung z) {
        return "Standardbuchung";
    }

    public String buche(Expresszustellung e) {
        return "Expressbuchung";
    }
}

Anlage 8 · Lager im Sortierzentrum

Im Sortierzentrum werden Posten in Regalen und Ablagen zwischengelagert. Dafür wird die generische Klasse Ablage<T> entwickelt; sie soll später auch für andere Elementtypen (z. B. Rollcontainer) verwendet werden.

Ablage- bezeichnung : string- kapazitaet : integer- elemente : List<T>+ Ablage(bezeichnung : string, kapazitaet : integer)+ ablegen(element : T) : boolean+ anzahl() : integer+ getElemente() : List<T>+ maximum(v : Vergleicher<T>) : TT«interface»Vergleicher+ vergleiche(a : T, b : T) : integerT«use»GewichtVergleicher+ vergleiche(a : Lagerposten, b : Lagerposten) : integerT = LagerpostenLagerposten{abstract}- postenNr : string- eingang : Datum- gewicht : double+ getPostenNr() : string+ getEingang() : Datum+ getGewicht() : double+ istFaellig(heute : Datum) : boolean {abstract}Retoure+ istFaellig(heute : Datum) : booleanZollposten- freigegeben : boolean+ istFaellig(heute : Datum) : booleanLagerauftrag- abholtermin : Datum+ istFaellig(heute : Datum) : booleanKonstruktoren der Lagerposten-Klassen sind nicht dargestellt.
Testdaten: Inhalt der Ablage „Regal A“ (Kapazität 6) in dieser Reihenfolge
postenNrKlasseeinganggewichtweitere Angabe
R-101Retoure20.09.20262.4–
R-102Retoure01.10.20260.8–
Z-201Zollposten05.10.202612.5freigegeben = true
Z-202Zollposten08.10.20263.1freigegeben = false
L-301Lagerauftrag28.09.202618.0abholtermin = 10.10.2026
L-302Lagerauftrag02.10.20267.5abholtermin = 12.10.2026

Anlage 9 · Quelltext der Klasse Tarifkonfiguration (Entwurf eines Kollegen, Java)

Die Tarife stehen in der Datei tarife.properties. Sie soll nur einmal eingelesen werden, und alle Programmteile sollen dieselben Werte verwenden. Die Methode ladeWert liest einen Wert aus der Datei; ihr Rumpf ist hier weggelassen und fehlerfrei.

 1  public class Tarifkonfiguration {
 2      private Tarifkonfiguration instanz;
 3      private double grundpreisPaket;
 4      private double kgZuschlag;
 5
 6      public Tarifkonfiguration() {
 7          grundpreisPaket = ladeWert("paket.grundpreis");
 8          kgZuschlag = ladeWert("paket.kgzuschlag");
 9      }
10
11      public Tarifkonfiguration getInstanz() {
12          instanz = new Tarifkonfiguration();
13          return instanz;
14      }
15
16      public double getGrundpreisPaket() { return grundpreisPaket; }
17      public double getKgZuschlag() { return kgZuschlag; }
18
19      private double ladeWert(String schluessel) { ... }   // Rumpf nicht dargestellt
20  }

Anlage 10 · Benachrichtigung der Empfänger

Empfänger werden über Zustellversuche und eingelegte Sendungen informiert. Im Kundenkonto ist der gewünschte Kanal ("EMAIL", "SMS" oder "PUSH") und die Kontaktangabe (E-Mail-Adresse, Mobilnummer oder Geräte-ID) hinterlegt (Methoden getKanal() : string und getKontakt() : string der Klasse Kunde).

Bisher steht an fünf Stellen im Programm eine Fallunterscheidung dieser Art:

wenn k.getKanal() = "EMAIL" dann
  b := new EmailBenachrichtigung(k.getKontakt())
sonst wenn k.getKanal() = "SMS" dann
  b := new SmsBenachrichtigung(k.getKontakt())
sonst wenn k.getKanal() = "PUSH" dann
  b := new PushBenachrichtigung(k.getKontakt())
Ende wenn

Anlage 11 · Tarife für Paketaufträge

Der Preis eines Paketauftrags hängt vom Tarif ab. Tarife ändern sich häufig, es kommen Aktionstarife hinzu, und bei einem Auftrag kann der Tarif nachträglich gewechselt werden (z. B. wenn ein Privatkunde ein Geschäftskonto eröffnet).

Tarif (Klasse)Preis in Abhängigkeit vom Gewicht g (kg)
Privattarifg ≤ 2: 4,99 · 2 < g ≤ 10: 7,49 · g > 10: 11,99
Geschaeftstarif3,20 + 0,25 je kg (3,20 + 0,25 · g)
Aktionstarifpauschal 3,99

Die Klasse Paketauftrag speichert das Gewicht und den aktuell gültigen Tarif. Sie hat einen Konstruktor Paketauftrag(gewicht : double, strategie : Preisstrategie), die Methode setStrategie(s : Preisstrategie) : void zum Wechseln des Tarifs und die Methode berechnePreis() : double. Alle Tarifklassen realisieren das Interface Preisstrategie, dessen einzige Methode zu einem Gewicht den Preis liefert.

Anlage 12 · Sendungsverfolgung

Jede Sendung hat einen Sendungsstatus (z. B. „Im Sortierzentrum“, „In Zustellung“, „In Paketstation eingelegt“). Sobald sich der Status ändert, sollen alle Anzeigen, die diese Sendung verfolgen, sofort aktualisiert werden: die Kunden-App des Empfängers und die Anzeige in der Halle, in der die Sendung bearbeitet wird. Später sollen weitere Anzeigen (z. B. das Kundenportal im Web) hinzukommen, ohne dass die Klasse Sendungsstatus geändert werden muss.

Für die Umsetzung wird das Observer-Muster in der Pull-Variante verwendet: Ein Observer erhält mit update() nur die Nachricht, dass sich etwas geändert hat, und holt sich den neuen Status selbst beim Subject.

Ablaufbeschreibung:

  1. Ein Client erzeugt ein Objekt status der Klasse Sendungsstatus für die Sendung KT-4711.
  2. Der Client erzeugt ein Objekt app der Klasse KundenApp und übergibt status an den Konstruktor. Im Konstruktor meldet sich app mit addObserver(this) bei status an.
  3. Ebenso erzeugt der Client ein Objekt halle der Klasse Hallenanzeige (Halle 3), das sich im Konstruktor bei status anmeldet.
  4. Der Client ruft setStatus("In Zustellung") bei status auf.
  5. Die Methode setStatus speichert den neuen Status und ruft danach die eigene Methode notifyObservers() auf.
  6. notifyObservers() ruft bei allen angemeldeten Observern – in der Reihenfolge der Anmeldung – die Methode update() auf.
  7. In update() holt sich der Observer mit getStatus() den neuen Status bei status, speichert ihn und ruft danach seine eigene Methode anzeigen() auf.
  8. Wenn alle Observer benachrichtigt sind, kehrt die Kontrolle über notifyObservers und setStatus zum Client zurück.

C · Aufgaben

Alle Aufgaben beziehen sich auf die Ausgangssituation in Anlage 1. Methoden sind – sofern nicht anders angegeben – in Pseudocode nach den Konventionen des Belegsatzes zu entwickeln; eine Programmiersprache (z. B. Java) ist ebenfalls zulässig. Für alle Attribute stehen Get-Methoden zur Verfügung. Summe aller Aufgaben: 178 Punkte (≈ 160 Minuten).

OOP-01 · Grundkonzepte: Klasse, Objekt, Kapselung

Aufgabe OOP-01.1 ★☆☆ Grundlagen 6 Punkte

Im Teilprojekt Paketstationen wird die Klasse Paketfach entworfen (Anlage 4). Die Paketstation „Hafenstraße“ hat 24 Fächer.

2 Pa) Erläutern Sie den Unterschied zwischen einer Klasse und einem Objekt am Beispiel der Paketfächer.
3 Pb) Die Attribute der Klasse Paketfach sollen gekapselt werden. Erläutern Sie das Prinzip der Datenkapselung am Beispiel der Klasse Paketfach und nennen Sie zwei Vorteile, die sich daraus für diese Klasse ergeben.
1 Pc) Nennen Sie die Aufgabe eines Konstruktors.
Aufgabe OOP-01.2 ★★☆ Anwendung 10 Punkte

Ein Kollege testet die Klasse Etikett (Anlage 3) mit folgendem Programmausschnitt:

Etikett e1 = new Etikett("Ahrens", 2.5);
Etikett e2 = new Etikett("Bode");
Etikett e3 = e1;
e3.setGewicht(4.0);
e2.setGewicht(40.0);
Etikett e4 = new Etikett("Ahrens", 4.0);
Etikett e5 = new Etikett("Cramer", 0.0);

System.out.println(e1);                    // (1)
System.out.println(e2);                    // (2)
System.out.println(e1 == e3);              // (3)
System.out.println(e1 == e4);              // (4)
System.out.println(e1.equals(e4));         // (5)
System.out.println(Etikett.getAnzahl());   // (6)
6 Pa) Geben Sie die Ausgaben (1) bis (6) an.
Nr.Ausgabe
(1)
(2)
(3)
(4)
(5)
(6)
2 Pb) Erläutern Sie, warum die Ausgaben (4) und (5) unterschiedlich sind, obwohl e1 und e4 denselben Empfänger und dasselbe Gewicht haben.
2 Pc) Begründen Sie, warum das Attribut naechsteNr als static deklariert ist, und beschreiben Sie, was sich ändern würde, wenn es ein normales Attribut wäre.
Aufgabe OOP-01.3 ★★★ Prüfungsniveau 16 Punkte

Sie sollen die Klasse Paketfach für KraniTrack entwerfen und implementieren. Die Anforderungen stehen in Anlage 4, die benötigte Klasse Sendung in Anlage 2.

7 Pa) Erstellen Sie ein UML-Klassendiagramm der Klasse Paketfach mit allen Attributen, Konstruktoren und Methoden einschließlich Datentypen, Parametern und Sichtbarkeiten. Kennzeichnen Sie statische Elemente. Zeichnen Sie außerdem die Klasse Sendung (nur mit Namen) und die Beziehung zwischen beiden Klassen ein.
3 Pb) Implementieren Sie beide Konstruktoren. Der Konstruktor mit nur einem Parameter soll den anderen Konstruktor verwenden.
3 Pc) Implementieren Sie die Methode einlegen.
3 Pd) Implementieren Sie die Methode entnehmen.

Angelehnt an: S26 Teil 2, Aufgabe 2a (Klasse mit vollständigen Signaturen und Sichtbarkeiten) und S24 Teil 1, Aufgabe 3bc (überladener Konstruktor)

OOP-02 · Vererbung, abstrakte Klassen und Interfaces

Aufgabe OOP-02.1 ★☆☆ Grundlagen 6 Punkte
3 Pa) Kreuzen Sie an, ob die Aussage auf eine abstrakte Klasse, auf ein Interface, auf beide oder auf keines von beiden zutrifft (Java).
Aussageabstrakte KlasseInterfacebeidekeines
1. Mit new können direkt Objekte davon erzeugt werden.
2. Es kann Attribute besitzen, deren Werte sich von Objekt zu Objekt unterscheiden.
3. Eine Klasse kann gleichzeitig mehrere davon erweitern bzw. implementieren.
4. Es kann als Datentyp für Variablen, Parameter und Listen verwendet werden.
5. Es kann einen Konstruktor besitzen, der von Unterklassen mit super(…) aufgerufen wird.
6. Es kann Methoden ohne Rumpf vorgeben, die jede konkrete Klasse implementieren muss.
3 Pb) Die Klassen Etikett, Abholschein und Ruecksendeschein sollen über eine einheitliche Methode drucken(drucker : string) : boolean ausgedruckt werden können. Die Klassen haben sonst keine gemeinsamen Attribute; Abholschein erbt bereits von der Klasse Dokument. Entscheiden Sie, ob dafür eine abstrakte Klasse Druckbar oder ein Interface Druckbar verwendet werden sollte, und begründen Sie Ihre Entscheidung mit zwei Argumenten.

Angelehnt an: S22 Teil 1, Aufgabe 3bc (Realisierung eines Interfaces erläutern)

Aufgabe OOP-02.2 ★★☆ Anwendung 10 Punkte

Für den Fuhrpark liegen die Klassen aus Anlage 5 vor.

4 Pa) Geben Sie alle Ausgaben in der richtigen Reihenfolge an, die bei der Ausführung der folgenden Anweisungen entstehen.
Fahrzeug f1 = new Lastenrad("LR-07");
Fahrzeug f2 = new Kuehltransporter("KT-02", 180);
5 Pb) Ein Kollege hat die Klasse EScooter ergänzt und eine Testanweisung geschrieben. Der Compiler meldet fünf Fehler. Nennen Sie die fünf Fehler (mit Zeilennummer) und beschreiben Sie jeweils eine Korrektur.
 1  public class EScooter extends Fahrzeug {
 2      public EScooter(String kennung) {
 3          reichweiteKm = 35;
 4          System.out.println("E-Scooter bereit");
 5      }
 6
 7      @Override
 8      public String info() {
 9          return "E-Scooter " + kennung;
10      }
11  }
12
13  // in einer Testklasse:
14  Fahrzeug reserve = new Fahrzeug("RES-1", 50);
ZeileFehlerKorrektur
1 Pc) Erläutern Sie, warum ein Kuehltransporter-Objekt sowohl einer Variablen vom Typ Fahrzeug als auch einer Variablen vom Typ Kuehlbar zugewiesen werden kann.
Aufgabe OOP-02.3 ★★★ Prüfungsniveau 22 Punkte

Die bisherige Klasse Postsendung (Anlage 6) arbeitet mit dem Attribut typ und enthält in vier Methoden dieselbe Fallunterscheidung. Da weitere Sendungsarten geplant sind, soll die Klasse mithilfe von Vererbung und Polymorphie umgebaut werden.

4 Pa) Erläutern Sie zwei Nachteile der bisherigen Umsetzung im Hinblick auf Wartbarkeit und Erweiterbarkeit.
9 Pb) Erstellen Sie ein UML-Klassendiagramm für einen Entwurf ohne Fallunterscheidungen nach typ. Tragen Sie Attribute, Konstruktoren und Methoden mit vollständiger Signatur und Sichtbarkeit ein.
Hinweise:
  • Machen Sie die Klasse Postsendung abstrakt und entfernen Sie das Attribut typ.
  • Verwenden Sie die Unterklassen Brief, Paket und Expresssendung.
  • Die Versicherung wird über das Interface Versicherbar mit der Methode berechneVersicherung modelliert. Es wird nur von den Sendungsarten realisiert, die versichert werden können.
5 Pc) Implementieren Sie die Klasse Paket mit ihrem Konstruktor und allen Methoden, die sie implementieren muss.
4 Pd) Die Klasse Paket erbt von Postsendung und realisiert zusätzlich Versicherbar. Erläutern Sie, warum das in Java möglich ist, eine Klasse aber nicht gleichzeitig von zwei Klassen erben darf.

Angelehnt an: S23 Teil 1, Aufgabe 2b (Polymorphie statt redundanter Auswahlstrukturen) und S26 Teil 2, Aufgabe 2a

OOP-03 · Polymorphie, Überladen/Überschreiben, Generics

Aufgabe OOP-03.1 ★☆☆ Grundlagen 6 Punkte
3 Pa) Erläutern Sie den Begriff Polymorphie am Beispiel der Klassen Zustellung und Expresszustellung (Anlage 7).
3 Pb) Entscheiden Sie für die folgenden Methodenpaare, ob Überladen oder Überschreiben vorliegt, und begründen Sie Ihre Entscheidung kurz.
MethodenpaarÜberladen / ÜberschreibenBegründung
1. Klasse Paketstation: suche(fachNr : integer) : Paketfach und suche(sendungsNr : string) : Paketfach
2. Oberklasse Zustellung: gebuehr() : double;
Unterklasse Expresszustellung: gebuehr() : double
3. Oberklasse Fahrzeug: planeTour(km : integer) : boolean;
Unterklasse Kuehltransporter: planeTour(km : integer, temperatur : integer) : boolean

Angelehnt an: S23 Teil 1, Aufgabe 2ba (Polymorphie erläutern)

Aufgabe OOP-03.2 ★★☆ Anwendung 10 Punkte

Für die Abrechnung von Zustellarten liegen die Klassen aus Anlage 7 vor. Gegeben ist folgender Programmausschnitt:

Zustellung a = new Zustellung();
Zustellung b = new Expresszustellung();
Zustellung c = new Nachtzustellung();
Expresszustellung d = new Nachtzustellung();
Kasse k = new Kasse();

System.out.println(b.beschreibung());   // (1)
System.out.println(b.quittung());       // (2)
System.out.println(c.quittung());       // (3)
System.out.println(a.quittung());       // (4)
System.out.println(k.buche(b));         // (5)
System.out.println(k.buche(d));         // (6)
6 Pa) Geben Sie die Ausgaben (1) bis (6) an.
Nr.Ausgabe
(1)
(2)
(3)
(4)
(5)
(6)
2 Pb) Die zusätzliche Anweisung b.zeitfenster(); wird vom Compiler abgelehnt, obwohl b auf eine Expresszustellung verweist. Begründen Sie das und geben Sie Anweisungen an, mit denen das Zeitfenster von b sicher ausgegeben werden kann.
2 Pc) Beschreiben Sie, was beim Übersetzen und beim Ausführen der Anweisung Expresszustellung x = (Expresszustellung) a; geschieht.

Angelehnt an: S26 Teil 2, Aufgabe 2bb (dynamische Bindung)

Aufgabe OOP-03.3 ★★★ Prüfungsniveau 18 Punkte

Im Sortierzentrum werden Posten in Ablagen zwischengelagert. Verwenden Sie die Klassen aus Anlage 8 und die Hilfsklassen aus Anlage 2.

3 Pa) Erläutern Sie, was eine generische Klasse wie Ablage<T> ist, und nennen Sie einen Vorteil gegenüber einer Klasse, die ihre Elemente als Object speichert.
4 Pb) Implementieren Sie den Konstruktor und die Methode ablegen(element : T) : boolean der Klasse Ablage<T>. Ein Element wird nur abgelegt, wenn die Kapazität noch nicht erreicht ist und das Element noch nicht in der Ablage liegt. Die Methode gibt zurück, ob das Element abgelegt wurde.
4 Pc) Implementieren Sie die Methode faelligePosten(ablage : Ablage<Lagerposten>, heute : Datum) : List<Lagerposten> der Klasse Lagerverwaltung. Sie liefert eine neue Liste mit allen Posten der Ablage, die heute fällig sind. Für die Testdaten aus Anlage 8 und heute = 10.10.2026 enthält das Ergebnis die Posten R-101, Z-201 und L-301.
3 Pd) In faelligePosten wird für jeden Posten dieselbe Anweisung istFaellig(heute) aufgerufen, ausgeführt wird aber je nach Posten eine andere Prüfung. Nennen und erläutern Sie den Mechanismus, der das bewirkt.
4 Pe) Implementieren Sie die Methode maximum(v : Vergleicher<T>) : T der Klasse Ablage<T>. Sie liefert das größte Element der Ablage gemäß dem übergebenen Vergleicher, bei einer leeren Ablage null. Für „Regal A“ und einen GewichtVergleicher ist das Ergebnis der Posten L-301.

Angelehnt an: W22 Teil 2, Aufgabe 1c/1d (generische Klasse, Vergleichsfunktion) und S26 Teil 2, Aufgabe 2b (polymorpher Aufruf in einer Container-Klasse)

OOP-04 · Entwurfsmuster: Grundlagen, Kategorien, Erzeugungsmuster

Aufgabe OOP-04.1 ★☆☆ Grundlagen 6 Punkte

Die Lorenz Software GmbH möchte bei der Entwicklung von KraniTrack konsequent Entwurfsmuster einsetzen.

4 Pa) Beschreiben Sie zwei Vorteile, die sich durch die Verwendung von Entwurfsmustern für das Entwicklungsteam von KraniTrack ergeben.
2 Pb) Ordnen Sie die Entwurfsmuster der passenden Kategorie zu (je ein Kreuz).
EntwurfsmusterErzeugungsmusterStrukturmusterVerhaltensmuster
Singleton
Fassade
Strategie
Fabrikmethode

Angelehnt an: W24 Teil 1, Aufgabe 4ba/4bb

Aufgabe OOP-04.2 ★★☆ Anwendung 10 Punkte

Die Klasse Tarifkonfiguration soll nach dem Singleton-Muster umgesetzt werden. Ein Kollege hat einen ersten Entwurf geschrieben (Anlage 9).

2 Pa) Beschreiben Sie den Zweck des Singleton-Musters am Beispiel der Tarifkonfiguration.
6 Pb) Der Entwurf in Anlage 9 erfüllt das Singleton-Muster nicht. Nennen Sie die vier Fehler mit Zeilennummer und geben Sie jeweils die korrigierte Zeile bzw. Anweisung an.
ZeileFehlerKorrektur
2 Pc) Auf dem Server von KraniTrack greifen viele Threads gleichzeitig auf die Tarifkonfiguration zu. Beschreiben Sie ein Problem, das dabei in der (korrigierten) Methode getInstanz auftreten kann, und nennen Sie eine Lösung.
Aufgabe OOP-04.3 ★★★ Prüfungsniveau 16 Punkte

Die Benachrichtigung der Empfänger (Anlage 10) soll überarbeitet werden. Statt der fünf verteilten Fallunterscheidungen soll eine statische Fabrikmethode erzeuge(kanal : string, ziel : string) in der abstrakten Klasse Benachrichtigung das passende Objekt erzeugen.

3 Pa) Erläutern Sie zwei Vorteile der Fabrikmethode gegenüber den bisherigen Fallunterscheidungen.
7 Pb) Erstellen Sie ein UML-Klassendiagramm mit den Klassen Benachrichtigung, EmailBenachrichtigung, SmsBenachrichtigung, PushBenachrichtigung und Zustellservice. Tragen Sie die Attribute und Methoden mit vollständiger Signatur und Sichtbarkeit ein und kennzeichnen Sie abstrakte und statische Elemente. Zeichnen Sie die Beziehungen ein (ohne Multiplizitäten).
4 Pc) Implementieren Sie die Fabrikmethode erzeuge. Für einen unbekannten Kanal soll null zurückgegeben werden.
2 Pd) Für Versandetiketten gibt es viele optionale Angaben (Hinweistext, Nachnahmebetrag, Wunschtag, Ablageort, Rücksendeadresse …). Ein Kollege schlägt dafür das Erzeugungsmuster Builder vor. Beschreiben Sie, warum ein Builder hier besser geeignet ist als ein Konstruktor mit allen Angaben als Parameter.

Angelehnt an: S23 Teil 1, Aufgabe 2bb/2bc (statische Fabrikmethode modellieren und implementieren)

OOP-05 · Verhaltens- und Strukturmuster

Aufgabe OOP-05.1 ★☆☆ Grundlagen 6 Punkte
3 Pa) Nennen Sie für jede Situation aus KraniTrack ein geeignetes Entwurfsmuster.
SituationEntwurfsmuster
1. Die Partner-Spedition liefert Sendungsdaten über ihre Klasse SpeditionApi mit der Methode holeStatusXml(auftragsId). KraniTrack erwartet aber das Interface Statusquelle mit getStatus(sendungsNr) : string. Die Klasse der Spedition darf nicht verändert werden.
2. Für eine Einlieferung am Schalter sind fünf Teilsysteme nötig (Waage, Tarif, Etikettendruck, Zahlung, Sendungsverfolgung). Die Schalter-App soll dafür nur eine einzige, einfache Methode einliefern(…) aufrufen.
3. Zu einer Sendung können Zusatzleistungen (Einschreiben, Nachnahme, Versicherung) beliebig kombiniert werden. Jede erhöht den Preis; für die Kombinationen sollen keine eigenen Unterklassen entstehen.
3 Pb) Das Kundenportal von KraniTrack wird nach dem MVC-Muster aufgebaut. Beschreiben Sie die Aufgaben von Model, View und Controller jeweils mit einem Beispiel aus dem Kundenportal (Sendungsverfolgung).

Angelehnt an: W24 Teil 1, Aufgabe 4bc (geeignetes Muster nennen)

Aufgabe OOP-05.2 ★★☆ Anwendung 10 Punkte

Die Preisberechnung für Paketaufträge (Anlage 11) wird nach dem Strategie-Muster umgesetzt. Ein Teil des Klassendiagramms liegt vor.

4 Pa) Ergänzen Sie im folgenden Klassendiagramm die fehlende Methode des Interfaces Preisstrategie und alle Beziehungen zwischen den Klassen.
Paketauftrag- gewicht : double- strategie : Preisstrategie+ Paketauftrag(gewicht : double, strategie : Preisstrategie)+ setStrategie(s : Preisstrategie) : void+ berechnePreis() : double«interface»PreisstrategiePrivattarif+ preis(gewicht : double) : doubleGeschaeftstarif+ preis(gewicht : double) : doubleAktionstarif+ preis(gewicht : double) : double
4 Pb) Implementieren Sie die Methode preis der Klasse Privattarif und die Methode berechnePreis der Klasse Paketauftrag.
2 Pc) Ein Kollege schlägt vor, stattdessen für jeden Tarif eine Unterklasse von Paketauftrag zu bilden (PrivatPaketauftrag, …). Erläutern Sie einen Vorteil des Strategie-Musters gegenüber diesem Vorschlag.
Aufgabe OOP-05.3 ★★★ Prüfungsniveau 26 Punkte

Die Sendungsverfolgung (Anlage 12) wird mit dem Observer-Muster umgesetzt.

4 Pa) Erläutern Sie die Funktionsweise des Observer-Musters und begründen Sie, warum es für die Sendungsverfolgung geeignet ist.
5 Pb) Der folgende Entwurf ist unvollständig. Ergänzen Sie alle Beziehungen zwischen den Klassen (mit Multiplizität an der Beziehung von Sendungsstatus zu den Observern) sowie die fehlenden Methoden in den Klassen Sendungsstatus, KundenApp und Hallenanzeige.
«interface»Subject+ addObserver(o : Observer) : void+ removeObserver(o : Observer) : void+ notifyObservers() : void«interface»Observer+ update() : voidSendungsstatus- sendungsNr : string- status : string- observers : List<Observer>+ Sendungsstatus(sendungsNr : string)+ getSendungsNr() : string+ getStatus() : string+ setStatus(status : string) : voidKundenApp- quelle : Sendungsstatus- letzteMeldung : string+ KundenApp(quelle : Sendungsstatus)+ anzeigen() : voidHallenanzeige- quelle : Sendungsstatus- hallenNr : integer- zeile : string+ Hallenanzeige(quelle : Sendungsstatus, hallenNr : integer)+ anzeigen() : void
3 Pc) Sendungsstatus kennt die Klassen KundenApp und Hallenanzeige nicht, sondern nur das Interface Observer. Erläutern Sie, welchen Vorteil das hat.
8 Pd) Ergänzen Sie das Sequenzdiagramm gemäß der Ablaufbeschreibung in Anlage 12 um alle noch fehlenden Aufrufe (ab Schritt 4) mit Aktivierungsbalken, Nachrichten und Antworten inklusive Beschriftung. Die Schritte 1 bis 3 sind bereits dargestellt.
Clientcreatestatus : Sendungsstatusnew KundenApp(status)app : KundenAppaddObserver(app)new Hallenanzeige(status, 3)halle : HallenanzeigeaddObserver(halle)ab hier ergänzen
4 Pe) Implementieren Sie die Methoden setStatus(status : string) : void und notifyObservers() : void der Klasse Sendungsstatus sowie die Methode update() : void der Klasse KundenApp.
2 Pf) Beschreiben Sie, wie sich die Methode update und der Ablauf ändern würden, wenn statt der Pull- die Push-Variante verwendet wird.

Angelehnt an: S22 Teil 1, Aufgabe 3 (Observer: Klassendiagramm, Realisierung, Sequenzdiagramm, notifyObservers) und W24 Teil 1, Aufgabe 4bc

Originalaufgaben zum Üben

Diese echten Prüfungsaufgaben passen zu diesem Block (Dateien im Ordner Prüfungsvorbereitung). Lösungen gibt es nur für 2023 Sommer, 2023 Winter, 2024 Sommer und 2025 Winter (S23 und W25 vom u-form-Verlag).

PrüfungAufgabeThemaDatei · Lösung
Sommer 2022T1 A3 (30 P)Observer durchgängig: Klassendiagramm mit Sichtbarkeiten, Beziehung zum Observer-Interface, Interface-Methoden, Realisierung erläutern, Sequenzdiagramm, notifyObservers in Pseudocode2022/sommer/AE 2022 Sommer Teil 1.pdf · keine Lösung
Winter 2022T2 A1c, A1d (10 P)generische Klasse erläutern, Sortieren einer List<T> mit Vergleichsfunktion (A1a/b: Bubblesort, Comparator)2022/winter/AE 2022 Winter Teil 2.pdf · keine Lösung
Sommer 2023T1 A2b (22 P)Polymorphie erläutern, abstrakte Klasse statt redundanter Fallunterscheidungen, statische Fabrikmethode modellieren und implementieren, überschreibende Methoden2023/sommer/AE Sommer 2023 Teil 1.pdf · Lösung: AE Sommer 2023 Teil 1+2 Lösungen.pdf
Sommer 2024T1 A3b (15 P)Methoden zu einem Klassendiagramm mit Listen; Objekt über überladenen Konstruktor erzeugen (A3bc)2024/sommer/AE Sommer 2024 Teil 1+2+3.pdf · Lösung: AE Sommer 2024 Teil 1 Lösungen.pdf
Winter 2024T1 A4b (10 P)Vorteile von Entwurfsmustern, Kategorien mit Beispielen, Observer wählen und begründen2024/winter/AE Winter 2024 Teil 1.pdf · keine Lösung
Sommer 2026T2 A2 (20 P)Klassendiagramm mit abstrakter Klasse, Vererbung und Aggregation; Methode aufgabenHeute in Pseudocode; dynamische Bindung erklären2026/sommer/AE Sommer 2026 Teil 2.pdf · keine Lösung
Winter 2021T1 A3b (7 P)Klasse mit Attributen und Methoden entwerfen2021/winter/AE 2021 Winter Teil 1.pdf · keine Lösung
Winter 2024T1 A3 (25 P)Klassendiagramm ergänzen, Aggregation und Komposition, Multiplizitäten (Schwerpunkt Block 03, gute Ergänzung)2024/winter/AE Winter 2024 Teil 1.pdf · keine Lösung