Entwurfsmuster: Strategie, Zustand, Proxy und Dekorierer

https://bildung.social/@oerinformatik/

https://oer-informatik.de/entwurfsmuster

Entwurfsmuster, Design Patterns, GoF, Strategie, Zustand, Proxy, Dekorierer, Adapter, Singleton, Polymorphie

tl/dr; (ca. 29 min Lesezeit): Entwurfsmuster (design patterns) sind bewährte Lösungsschablonen für wiederkehrende Probleme im objektorientierten Entwurf. Der Artikel erklärt, wozu Muster gut sind, wie sie in Erzeugungs-, Struktur- und Verhaltensmuster eingeteilt werden, und stellt vier Muster ausführlich vor, die in Aufgaben immer wieder vorkommen: Strategie, Zustand, Proxy und Dekorierer – jeweils mit Problem, Klassendiagramm, Pseudocode bzw. Java und typischen Fehlern. Zum Schluss geht es um die Frage, welches Muster zu welcher Situation passt [AI-Assisted]. (Zuletzt geändert am 28.10.2026)

Dieser Artikel ist Teil einer Artikelreihe zur objektorientierten Modellierung: Klassendiagramm / Vererbung / Abstrakte Klassen und Interfaces / Polymorphie / Entwurfsmuster / Übungsaufgaben zu Entwurfsmustern

Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.

Dieser Artikel baut auf der Kann-Liste der Artikel Abstrakte Klassen und Interfaces und Polymorphie auf.

Als Grundlagen zu Entwurfsmustern kann ich nach Durchlesen des Artikels…

  • … erklären, was ein Entwurfsmuster ist, und Vorteile der Verwendung von Entwurfsmustern beschreiben.

  • … die Kategorien Erzeugungs-, Struktur- und Verhaltensmuster unterscheiden und je Kategorie Beispiele nennen.

  • … ein Entwurfsmuster nennen und mit Problem, Lösungsidee und Aufbau beschreiben.

  • … ein vorgegebenes Klassendiagramm nach dem Strategie-, Zustands-, Proxy- oder Dekorierer-Muster um Klassen, Methoden und Beziehungen (Realisierung, Vererbung, Aggregation/Komposition) ergänzen.

  • … im Pseudocode Methoden formulieren, die an ein Strategie- oder Zustandsobjekt delegieren (strategie.berechne(this)), und eine Strategie im Konstruktor oder zur Laufzeit per Setter austauschen.

  • … den Anfangszustand im Konstruktor setzen und einen Zustandswechsel mit einer Kontrollstruktur formulieren.

  • … am Zustands- oder Strategiemuster erläutern, was Polymorphie bedeutet.

  • … Konstruktoren eines Proxys oder Dekorierers implementieren (inklusive Aufruf des Basisklassenkonstruktors) und ein Objekt mit mehreren Dekorierern umhüllen.

  • … für eine beschriebene Situation ein geeignetes Entwurfsmuster nennen und die Wahl begründen.

Darüber hinaus kann ich als Expertenwissen…

  • … Zustandsobjekte als Singleton mit privatem Konstruktor und statischer Zugriffsmethode umsetzen und eine statische Fabrikmethode (getInstance) implementieren.

  • … Strategie und Zustand sowie Proxy, Dekorierer und Adapter trotz ähnlicher Struktur nach ihrem Zweck unterscheiden.

  • … Schutz-, virtuelle und Remote-Proxys unterscheiden und englischsprachige Musterbeschreibungen auswerten.

  • … Nachteile von Entwurfsmustern (mehr Klassen, Indirektion, Überdimensionierung) abwägen.

Wenn Du schon ein paar objektorientierte Programme geschrieben hast, kennst Du das Gefühl: Ein Problem kommt Dir bekannt vor. Wieder soll ein Algorithmus austauschbar sein, wieder soll ein Objekt je nach Situation anders reagieren, wieder soll ein Zugriff kontrolliert werden. Andere haben diese Probleme schon tausendfach gelöst – und aus diesen Lösungen sind Entwurfsmuster entstanden.

Was ist ein Entwurfsmuster?

Ein Entwurfsmuster (design pattern) ist eine bewährte, allgemein beschriebene Lösung für ein wiederkehrendes Problem im Softwareentwurf. Es ist kein fertiger Code, den man kopiert, sondern eine Schablone: Welche Klassen und Interfaces gibt es, welche Rollen haben sie, wie hängen sie zusammen, wie arbeiten sie zusammen?

Bekannt geworden sind Entwurfsmuster durch das Buch „Design Patterns – Elements of Reusable Object-Oriented Software“ von Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides (1994). Die vier Autoren werden oft „Gang of Four“ (GoF) genannt, die 23 dort beschriebenen Muster entsprechend „GoF-Muster“.

Eine Musterbeschreibung hat immer dieselben Bestandteile:

  • Name: Er macht das Muster zum Fachbegriff („Hier nehmen wir eine Strategie.“).

  • Problem: In welcher Situation hilft das Muster?

  • Lösung: Welche Klassen, Interfaces und Beziehungen gibt es, und wie arbeiten sie zusammen? Meist als UML-Klassendiagramm, oft ergänzt durch ein Sequenzdiagramm.

  • Konsequenzen: Was gewinnt man, was nimmt man in Kauf?

Vorteile von Entwurfsmustern

  • Bewährte Lösungen: Die Muster sind vielfach erprobt. Man muss das Rad nicht neu erfinden und vermeidet typische Entwurfsfehler.

  • Gemeinsame Fachsprache: „Der Warenkorb nutzt eine Strategie für die Versandkosten“ ersetzt eine halbe Seite Erklärung. Das erleichtert die Kommunikation im Team und die Dokumentation.

  • Wartbarkeit und Erweiterbarkeit: Die meisten Muster trennen das, was sich ändert, von dem, was gleich bleibt. Neue Varianten lassen sich ergänzen, ohne bestehenden Code zu ändern (Offen-geschlossen-Prinzip).

  • Lose Kopplung: Klassen kennen einander nur über Interfaces oder abstrakte Klassen. Implementierungen sind austauschbar und einzeln testbar.

  • Wiederverwendbarkeit: Die entstehenden Klassen sind klein und klar abgegrenzt und lassen sich in anderen Zusammenhängen wiederverwenden.

  • Schnellere Einarbeitung: Wer ein Muster erkennt, versteht fremden Code schneller – viele Frameworks sind aus Mustern aufgebaut (z. B. die Ein- und Ausgabeströme in Java als Dekorierer).

Entwurfsmuster haben auch einen Preis: Es entstehen mehr Klassen und Interfaces, Aufrufe laufen über zusätzliche Objekte (Indirektion), und für ein kleines Problem kann ein Muster überdimensioniert sein. Ein Muster lohnt sich, wenn das Problem, für das es gedacht ist, wirklich vorliegt.

Kategorien von Entwurfsmustern

Die GoF teilen ihre Muster nach dem Zweck in drei Kategorien ein:

Kategorien der GoF-Entwurfsmuster mit Beispielen
Kategorie Zweck Beispiele
Erzeugungsmuster (creational patterns) Wie und von wem werden Objekte erzeugt? Die Erzeugung wird vom Rest des Programms entkoppelt. Fabrikmethode (Factory Method), Abstrakte Fabrik (Abstract Factory), Singleton, Erbauer (Builder), Prototyp
Strukturmuster (structural patterns) Wie werden Klassen und Objekte zu größeren Strukturen zusammengesetzt? Proxy (Stellvertreter), Dekorierer (Decorator), Adapter, Fassade (Facade), Kompositum (Composite), Brücke (Bridge)
Verhaltensmuster (behavioral patterns) Wie arbeiten Objekte zusammen, wie werden Aufgaben und Algorithmen verteilt? Strategie (Strategy), Zustand (State), Beobachter (Observer), Schablonenmethode (Template Method), Befehl (Command), Iterator

Die Mindmap fasst die Kategorien mit wichtigen Mustern zusammen. Grün markiert sind die Muster, die dieser Artikel ausführlich behandelt, rosa die Muster mit eigenem Artikel.

Mindmap der GoF-Entwurfsmuster: Erzeugungsmuster (u. a. Fabrikmethode, Singleton), Strukturmuster (u. a. Proxy, Dekorierer, Adapter, Fassade), Verhaltensmuster (u. a. Strategie, Zustand, Beobachter) und Nutzen von Entwurfsmustern (mit KI erstellt)

Wenn Du zu jeder Kategorie Beispiele nennen sollst, merke Dir diese sechs: Fabrikmethode und Singleton (Erzeugung), Adapter und Fassade (Struktur), Beobachter und Strategie (Verhalten).

Fabrikmethode und Beobachter sind so wichtig, dass sie eigene Artikel bekommen. Die folgenden vier Muster haben eines gemeinsam: Ein Objekt delegiert Arbeit an ein anderes Objekt, das es nur über ein Interface oder eine abstrakte Klasse kennt. Genau dieses Zusammenspiel aus Realisierung, Vererbung, Aggregation und Delegation wird in Aufgaben häufig abgefragt.

Strategie (Strategy)

Problem: Eine Klasse braucht einen Algorithmus, für den es mehrere Varianten gibt – und die Varianten ändern sich häufig oder sollen zur Laufzeit gewählt werden. Eine lange if-else-Kette in der Klasse würde bei jeder neuen Variante wachsen und die Klasse ständig ändern.

Lösungsidee: Jede Variante des Algorithmus wird in eine eigene Klasse ausgelagert. Alle Varianten implementieren ein gemeinsames Interface. Die nutzende Klasse (der Kontext) hält eine Referenz vom Typ dieses Interfaces und delegiert den Aufruf an das aktuell gesetzte Strategieobjekt.

Beispiel: Eine Navigations-App berechnet die Fahrtdauer je nach Verkehrsmittel unterschiedlich.

Strategiemuster am Beispiel einer Navigations-App: Navigation hält eine Referenz auf das Interface Routenstrategie, das von Auto, Fahrrad und ZuFuss realisiert wird (mit KI erstellt)

Die Rollen im Muster:

  • Kontext (Navigation): hält die Referenz strategie, bietet einen Setter zum Austauschen und eine Methode, die an die Strategie delegiert.

  • Strategie (Routenstrategie): Interface (oder abstrakte Klasse) mit der Methode des Algorithmus.

  • Konkrete Strategien (Auto, Fahrrad, ZuFuss): realisieren das Interface und implementieren die Methode jeweils anders. Im Klassendiagramm wird die Methode in jeder konkreten Klasse erneut notiert.

Die Beziehung vom Kontext zur Strategie ist eine Aggregation (hohle Raute am Kontext). Eine Komposition (gefüllte Raute) ist ebenfalls vertretbar, wenn das Strategieobjekt nur diesem Kontext gehört. Die konkreten Strategien sind über eine Realisierung (gestrichelte Linie, hohles Dreieck) mit dem Interface verbunden.

In Java sieht das so aus:

public interface Routenstrategie {
    double dauerInMinuten(double km);
}

public class Fahrrad implements Routenstrategie {
    public double dauerInMinuten(double km) {
        return km / 15.0 * 60.0;          // 15 km/h im Schnitt
    }
}

public class Navigation {
    private Routenstrategie strategie;

    public Navigation() {
        this.strategie = new Auto();       // Standardstrategie im Konstruktor
    }

    public void setStrategie(Routenstrategie s) {
        this.strategie = s;                // Austausch zur Laufzeit
    }

    public double berechneDauer(double km) {
        return strategie.dauerInMinuten(km);   // Delegation
    }
}

Und so wird die Strategie zur Laufzeit gewechselt:

Navigation navi = new Navigation();
double mitAuto = navi.berechneDauer(12.0);
navi.setStrategie(new Fahrrad());
double mitRad = navi.berechneDauer(12.0);   // 48.0

Im Pseudocode genügt für die Delegation eine Zeile, für den Austausch ebenfalls. Die Pseudocode-Beispiele kannst Du in vier Notationen anzeigen; die Notationen sind im Artikel Algorithmen mit Pseudocode dokumentieren beschrieben.

Funktion berechneDauer(km: double): double
    Rückgabe strategie.dauerInMinuten(km)
Ende berechneDauer

navi.setStrategie(new Fahrrad())

Braucht die Strategie Daten aus dem Kontext, übergibt der Kontext sich selbst mit this: Rückgabe strategie.berechne(this). Die Strategie kann dann über die öffentlichen Methoden des Kontexts (z. B. Getter) auf diese Daten zugreifen. Ist der Kontext eine Unterklasse, ruft ihr Konstruktor zuerst den Konstruktor der Basisklasse auf (super(...) bzw. „Aufruf Basisklassenkonstruktor“) und setzt danach die Standardstrategie.

Typische Fehler:

  • Die Strategie wird im Kontext mit if (art == "Fahrrad") ... ausgewählt – dann ist es kein Strategiemuster mehr.

  • Die Delegation vergisst das return oder ruft die Methode auf dem Interface statt auf der Referenz auf (Routenstrategie.dauerInMinuten(km)).

  • Im Klassendiagramm zeigt der Realisierungspfeil vom Interface zur Klasse statt umgekehrt, oder die Raute sitzt am Interface statt am Kontext.

Zustand (State)

Problem: Ein Objekt verhält sich je nach seinem Zustand völlig unterschiedlich. Jede Methode beginnt mit einer Fallunterscheidung über den aktuellen Zustand, und mit jedem neuen Zustand wachsen alle diese Fallunterscheidungen. Typisch ist das für Abläufe, die als UML-Zustandsdiagramm modelliert sind.

Lösungsidee: Jeder Zustand wird eine eigene Klasse. Alle Zustandsklassen erben von einer abstrakten Klasse (oder realisieren ein Interface) mit den zustandsabhängigen Methoden. Das Objekt mit dem Zustand (der Kontext) hält eine Referenz auf das aktuelle Zustandsobjekt und delegiert jeden Aufruf dorthin. Ein Zustandswechsel bedeutet: Der Referenz wird ein anderes Zustandsobjekt zugewiesen – meist durch das bisherige Zustandsobjekt selbst.

Beispiel: Ein Dokument in einem Redaktionssystem durchläuft die Zustände Entwurf, in Prüfung und veröffentlicht. weiter() schickt einen Entwurf in die Prüfung und ein geprüftes Dokument in die Veröffentlichung; ablehnen() schickt ein Dokument aus der Prüfung zurück in den Entwurf.

Zustandsmuster am Beispiel eines Dokuments: Dokument hält eine Referenz auf die abstrakte Klasse Dokumentzustand mit den Unterklassen Entwurf, InPruefung und Veroeffentlicht, jeweils als Singleton (mit KI erstellt)

Der Kontext setzt im Konstruktor den Anfangszustand und delegiert alle zustandsabhängigen Methoden. Dabei übergibt er sich selbst, damit das Zustandsobjekt den Zustand wechseln kann:

Konstruktor Dokument()
    zustand := Entwurf.getInstanz()
Ende Dokument

Prozedur weiter()
    zustand.weiter(this)
Ende weiter

In den konkreten Zuständen steht das Verhalten – und die Bedingung für den Zustandswechsel:

Klasse InPruefung
    Prozedur weiter(d: Dokument)
        d.setZustand(Veroeffentlicht.getInstanz())
    Ende weiter

    Prozedur ablehnen(d: Dokument)
        d.setZustand(Entwurf.getInstanz())
    Ende ablehnen
Ende Klasse

Klasse Veroeffentlicht
    Prozedur weiter(d: Dokument)
        // keine Wirkung: ein veröffentlichtes Dokument bleibt veröffentlicht
    Ende weiter
Ende Klasse

Hängt der Wechsel von einer Bedingung ab, steht im Zustandsobjekt eine Kontrollstruktur. Ein Beispiel: Ein Entwurf darf nur in die Prüfung, wenn er einen Titel hat.

Klasse Entwurf
    Prozedur weiter(d: Dokument)
        wenn d.getTitel() <> "" dann
            d.setZustand(InPruefung.getInstanz())
        Ende wenn
    Ende weiter
Ende Klasse

Zustände als Singleton

Zustandsobjekte haben meist keine eigenen Daten – alle Daten stehen im Kontext. Dann genügt ein einziges Objekt je Zustandsklasse, das sich alle Kontexte teilen. Dafür wird jede Zustandsklasse als Singleton (ein Erzeugungsmuster) umgesetzt:

  • Der Konstruktor ist privat (-Entwurf()). Von außen kann niemand new Entwurf() aufrufen.

  • Ein statisches Klassenattribut (-{static} instanz: Entwurf, im Diagramm unterstrichen) hält das einzige Objekt.

  • Eine statische Methode (+{static} getInstanz(): Entwurf) liefert dieses Objekt und erzeugt es beim ersten Aufruf.

public class Entwurf extends Dokumentzustand {
    private static Entwurf instanz;

    private Entwurf() { }

    public static Entwurf getInstanz() {
        if (instanz == null) {
            instanz = new Entwurf();
        }
        return instanz;
    }
    // weiter(), ablehnen(), getName() ...
}

Eine solche statische Methode, die statt eines Konstruktors ein Objekt liefert, heißt auch statische Fabrikmethode. Sie ist nicht zu verwechseln mit dem GoF-Erzeugungsmuster Fabrikmethode, bei dem Unterklassen entscheiden, welches Produkt erzeugt wird. Sie muss nicht immer ein Singleton liefern: Eine Klasse kann ihren Konstruktor verbergen und Objekte nur über eine Methode wie getInstance(...) herausgeben, die z. B. Parameter prüft oder ein Objekt eines passenden Untertyps erzeugt. Ihre Bestandteile: Eigenschaft static, Rückgabetyp der Klasse (oder ihres Interfaces), die nötigen Übergabeparameter und ein return new ....

Polymorphie am Zustandsmuster

Das Zustandsmuster ist ein Paradebeispiel für Polymorphie (Vielgestaltigkeit): Das Attribut zustand hat den statischen Typ Dokumentzustand, verweist zur Laufzeit aber auf ein Objekt vom Typ Entwurf, InPruefung oder Veroeffentlicht. Der Aufruf zustand.weiter(this) steht im Quelltext nur einmal. Welche Implementierung von weiter() ausgeführt wird, entscheidet sich erst zur Laufzeit anhand des tatsächlichen Objekts (späte oder dynamische Bindung). Derselbe Aufruf bewirkt also je nach Zustand etwas anderes – ohne eine einzige Fallunterscheidung im Kontext. Mehr dazu im Artikel Polymorphie.

Strategie oder Zustand?

Die Klassendiagramme von Strategie und Zustand sehen fast gleich aus. Der Unterschied liegt im Zweck und darin, wer wechselt:

Unterschiede zwischen Strategie- und Zustandsmuster
Merkmal Strategie Zustand
Was wird gekapselt? ein austauschbarer Algorithmus das Verhalten in einem Zustand
Wer wechselt das Objekt? meist der Client von außen (Setter, Konstruktor) meist die Zustandsobjekte selbst (Zustandsübergang)
Kennen sich die konkreten Klassen? nein, sie sind voneinander unabhängig ja, ein Zustand kennt seine Folgezustände
Typische Vorlage Anforderung „Algorithmus soll austauschbar sein“ UML-Zustandsdiagramm

Eine ausführliche Umsetzung des Zustandsmusters für eine Konsolenoberfläche in Java findest Du in der Artikelserie Konsolen-UI mit State-Pattern.

Proxy (Stellvertreter)

Problem: Der Zugriff auf ein Objekt soll kontrolliert werden – etwa, weil nicht jede*r alle Methoden aufrufen darf, weil das Erzeugen des Objekts teuer ist oder weil das Objekt auf einem anderen Rechner liegt. Die Klassen, die das Objekt benutzen, sollen davon aber nichts merken.

Lösungsidee: Ein Stellvertreter (Proxy) implementiert dasselbe Interface wie das echte Objekt. Der Client arbeitet nur mit dem Interface und weiß nicht, ob er mit dem Proxy oder dem echten Objekt spricht. Der Proxy hält eine Referenz auf das echte Objekt, führt vor oder nach der Weiterleitung eigene Logik aus (prüfen, verzögert erzeugen, protokollieren …) und leitet den Aufruf weiter.

Beispiel: Eine Galerie-App zeigt Vorschaulisten mit vielen Bildern. Die Bilddateien sind groß; sie sollen erst geladen werden, wenn ein Bild tatsächlich angezeigt wird.

Proxymuster am Beispiel einer Galerie: BildProxy und Bilddatei realisieren das Interface Bild; BildProxy hält eine Referenz auf Bilddatei und erzeugt sie erst beim Anzeigen (mit KI erstellt)

Die Rollen im Muster:

  • Subjekt (Bild): gemeinsames Interface von Proxy und echtem Objekt.

  • Echtes Subjekt (Bilddatei): erledigt die eigentliche Arbeit.

  • Proxy (BildProxy): realisiert ebenfalls das Interface, hält eine Referenz auf das echte Subjekt (Aggregation) und leitet weiter. Methoden, die er ohne das echte Objekt beantworten kann (hier getTitel()), beantwortet er selbst.

  • Client (Galerie): kennt nur das Interface.

Klasse BildProxy
    Konstruktor BildProxy(titel: string, pfad: string)
        this.titel := titel
        this.pfad := pfad
        echtesBild := null
    Ende BildProxy

    Funktion getTitel(): string
        Rückgabe titel                      // ohne die Datei zu laden
    Ende getTitel

    Prozedur anzeigen()
        wenn echtesBild = null dann
            echtesBild := new Bilddatei(titel, pfad)   // erst jetzt laden
        Ende wenn
        echtesBild.anzeigen()               // Weiterleitung
    Ende anzeigen
Ende Klasse

Je nach Zweck unterscheidet man verschiedene Arten von Proxys:

  • Virtueller Proxy: erzeugt ein aufwendiges Objekt erst, wenn es gebraucht wird (lazy initialization) – wie im Beispiel.

  • Schutzproxy (protection proxy): prüft vor der Weiterleitung, ob der Aufrufer die Methode nutzen darf (z. B. ob eine Person ein Profil sehen oder ändern darf). Ist der Zugriff nicht erlaubt, liefert er einen Ersatzwert oder eine Fehlermeldung, oder er wirft eine Ausnahme.

  • Remote-Proxy: vertritt ein Objekt auf einem anderen Rechner und verpackt Aufrufe in Netzwerkanfragen (z. B. ein Client-Stub für eine REST-API).

  • Caching-Proxy: merkt sich Ergebnisse und beantwortet wiederholte Anfragen ohne das echte Objekt.

Damit der Client nicht selbst entscheidet, ob er einen Proxy oder das echte Objekt erzeugt, bietet die Proxy-Klasse oft eine statische Fabrikmethode an, deren Rückgabetyp das Interface ist:

statisch Funktion erzeugeBild(titel: string, pfad: string): Bild
    Rückgabe new BildProxy(titel, pfad)
Ende erzeugeBild

Dekorierer (Decorator)

Problem: Ein Objekt soll um zusätzliche Fähigkeiten erweitert werden, und zwar in beliebigen Kombinationen. Mit Vererbung bräuchte man für jede Kombination eine eigene Unterklasse: Bei drei Zusatzfunktionen sind das schon 2³ = 8 Varianten, bei fünf bereits 32 („Klassenexplosion“). Außerdem steht bei Vererbung zur Übersetzungszeit fest, was ein Objekt kann.

Lösungsidee: Jede Zusatzfähigkeit wird eine Dekoriererklasse, die dasselbe Interface realisiert wie das Grundobjekt und ein Objekt dieses Interfaces umhüllt (wrapper). Ein Aufruf geht zuerst an den äußersten Dekorierer; er ruft dieselbe Methode beim umhüllten Objekt auf und ergänzt das Ergebnis. Da jeder Dekorierer selbst wieder das Interface realisiert, lassen sich Dekorierer beliebig ineinander verschachteln.

Beispiel: Meldungen eines Programms sollen wahlweise mit Zeitstempel, in Großschrift oder beidem ausgegeben werden.

Dekorierermuster am Beispiel von Meldungen: Textmeldung und die abstrakte Klasse Meldungsdekorierer realisieren das Interface Meldung; Meldungsdekorierer hält eine Referenz auf eine Meldung und hat die Unterklassen Zeitstempel und Grossschrift (mit KI erstellt)

Die Rollen im Muster:

  • Komponente (Meldung): gemeinsames Interface.

  • Konkrete Komponente (Textmeldung): das Grundobjekt, das dekoriert wird.

  • Dekorierer (Meldungsdekorierer): abstrakte Klasse, die das Interface realisiert und zugleich eine Referenz auf ein Objekt des Interfaces hält (Aggregation, Raute am Dekorierer). Das Attribut ist protected (#), damit die Unterklassen es nutzen können.

  • Konkrete Dekorierer (Zeitstempel, Grossschrift): erben vom Dekorierer und überschreiben die Methoden des Interfaces; im Klassendiagramm werden diese Methoden erneut notiert.

Konstruktoren werden nicht vererbt. Jeder konkrete Dekorierer braucht einen eigenen Konstruktor, der das zu umhüllende Objekt an den Konstruktor der Basisklasse weiterreicht:

Klasse Meldungsdekorierer
    Konstruktor Meldungsdekorierer(inhalt: Meldung)
        this.inhalt := inhalt
    Ende Meldungsdekorierer
Ende Klasse

Klasse Zeitstempel erbt von Meldungsdekorierer
    Konstruktor Zeitstempel(inhalt: Meldung)
        super(inhalt)                       // Aufruf des Basisklassenkonstruktors
    Ende Zeitstempel

    Funktion getText(): string
        Rückgabe aktuelleUhrzeit() & " " & inhalt.getText()
    Ende getText
Ende Klasse

Klasse Grossschrift erbt von Meldungsdekorierer
    Konstruktor Grossschrift(inhalt: Meldung)
        super(inhalt)
    Ende Grossschrift

    Funktion getText(): string
        Rückgabe inhalt.getText().toUpperCase()
    Ende getText
Ende Klasse

Der Client erzeugt zuerst das Grundobjekt und umhüllt es dann Schicht für Schicht. Die Referenz hat dabei immer den Typ des Interfaces:

m: Meldung := new Textmeldung("Server gestartet")
m := new Grossschrift(m)
m := new Zeitstempel(m)
Ausgabe m.getText()                     // "09:15 SERVER GESTARTET"

oder kürzer in einer Zeile: m : Meldung := new Zeitstempel(new Grossschrift(new Textmeldung("Server gestartet"))).

Der Aufruf m.getText() läuft von außen nach innen: Zeitstempel fragt Grossschrift, Grossschrift fragt Textmeldung. Die Ergebnisse werden auf dem Rückweg von innen nach außen ergänzt. Die Reihenfolge der Umhüllung kann deshalb das Ergebnis beeinflussen: new Grossschrift(new Zeitstempel(...)) würde auch den Zeitstempel in Großschrift ausgeben (bei Ziffern ohne sichtbaren Unterschied, bei „Uhr“ schon).

Bekannte Beispiele aus der Praxis sind die Ein- und Ausgabeströme in Java: new BufferedInputStream(new FileInputStream("daten.bin")) umhüllt einen Byte-Strom mit einem Puffer und ist selbst wieder ein InputStream; ebenso umhüllt new BufferedReader(new FileReader("daten.txt")) einen Reader. Ein InputStreamReader dagegen macht aus einem Byte-Strom (InputStream) einen Zeichenstrom (Reader) – er bietet eine andere Schnittstelle an und ist damit eher ein Adapter.

Proxy, Dekorierer oder Adapter?

Drei Strukturmuster haben ein „umhüllendes“ Objekt, das Aufrufe an ein anderes Objekt weiterleitet. Auch hier entscheidet der Zweck:

Proxy, Dekorierer und Adapter im Vergleich
Muster Schnittstelle nach außen Zweck Wer erzeugt das innere Objekt?
Proxy dieselbe wie das echte Objekt Zugriff kontrollieren (schützen, verzögern, entfernt aufrufen); fügt aus Sicht des Clients keine neue Funktion hinzu meist der Proxy selbst
Dekorierer dieselbe wie das umhüllte Objekt Funktion ergänzen, beliebig kombinierbar und verschachtelbar der Client, der die Hüllen zusammensteckt
Adapter eine andere als das umhüllte Objekt eine unpassende Schnittstelle anpassen, damit vorhandene Klassen zusammenarbeiten meist der Client

Ein Beispiel für einen Adapter: Eine Anwendung erwartet Temperaturen über getCelsius(), ein zugekaufter Sensor liefert nur readFahrenheit(). Eine Adapterklasse realisiert das erwartete Interface und rechnet intern um. Ein Dekorierer würde dagegen dasselbe Interface behalten und z. B. aus mehreren Messwerten einen Durchschnitt bilden.

Achtung: Auch in Aufgabentexten werden die Begriffe nicht immer sauber verwendet. Entscheide anhand der Struktur: Realisiert die Hülle dasselbe Interface, das sie auch umhüllt, und lässt sie sich verschachteln, ist es ein Dekorierer – auch wenn die Klasse „Filter“ oder „Wrapper“ heißt.

Welches Muster passt?

In Aufgaben sollst Du oft zu einer Situation ein geeignetes Muster nennen und Deine Wahl begründen. Eine gute Begründung nennt das Problem aus der Situation und wie das Muster es löst. Die folgende Übersicht hilft beim Zuordnen:

Typische Situationen und passende Entwurfsmuster
Situation (typische Formulierung) Muster Begründung (Kern)
Mehrere Algorithmen für dieselbe Aufgabe, „wechseln häufig“, „zur Laufzeit wählbar“, „entkoppelt“ Strategie Algorithmen in eigenen Klassen hinter einem Interface, Austausch per Setter ohne Änderung des Kontexts
Verhalten hängt vom Zustand ab, Ablauf als Zustandsdiagramm Zustand je Zustand eine Klasse, Wechsel durch Austausch des Zustandsobjekts statt Fallunterscheidungen
Zugriff prüfen, Objekt erst bei Bedarf laden, entferntes Objekt nutzen Proxy Stellvertreter mit gleichem Interface kontrolliert den Zugriff, Client bleibt unverändert
Zusatzfunktionen in beliebiger Kombination, ohne Unterklassen für jede Kombination Dekorierer Hüllen mit gleichem Interface, beliebig verschachtelbar, zur Laufzeit kombinierbar
Viele Objekte sollen bei einer Änderung automatisch aktualisiert werden (Anzeigen, Abonnenten) Beobachter Subjekt benachrichtigt alle angemeldeten Beobachter über ein Interface, lose Kopplung
Vorhandene Klasse hat eine unpassende Schnittstelle Adapter Übersetzt zwischen erwarteter und vorhandener Schnittstelle
Welche konkrete Klasse erzeugt wird, sollen Unterklassen entscheiden; neue Produktarten ohne Änderung des aufrufenden Codes Fabrikmethode Erzeugung in eine überschreibbare Methode verlagert, Rückgabetyp ist das gemeinsame Interface
Von einer Klasse darf es nur ein Objekt geben (Konfiguration, Verbindungspool) Singleton privater Konstruktor, statische Zugriffsmethode
Ein komplexes Teilsystem soll über eine einfache Schnittstelle nutzbar sein Fassade Eine Klasse bündelt die Aufrufe an viele Klassen des Teilsystems

Entwurfsmuster in Aufgaben

Aufgaben zu Entwurfsmustern folgen meist einem ähnlichen Ablauf. Diese Hinweise helfen Dir dabei:

  1. Klassendiagramm ergänzen: Erst die Rollen zuordnen (Kontext, Interface, konkrete Klassen, Hülle). Dann Beziehungen einzeichnen: Realisierung (gestrichelt, hohles Dreieck am Interface), Vererbung (durchgezogen, hohles Dreieck an der Oberklasse), Aggregation bzw. Komposition (Raute an der Klasse, die die Referenz hält). Zuletzt die Methoden des Interfaces in jeder realisierenden bzw. überschreibenden Klasse erneut eintragen – mit vollständiger Signatur.

  2. Pseudocode: Delegation mit Rückgabe (Rückgabe referenz.methode(this)), Konstruktoren mit Zuweisung der Attribute bzw. super(...), Objekterzeugung mit new. Bei Singletons nie new von außen, sondern Klasse.getInstanz().

  3. Erläutern: Fachbegriffe verwenden (Interface, Realisierung, Delegation, lose Kopplung, Polymorphie, späte Bindung) und am gegebenen Beispiel zeigen, was sie bedeuten.

  4. Englische Fachtexte: Musterbeschreibungen stammen oft aus englischsprachigen Quellen. Die wichtigsten Vokabeln: interface (Schnittstelle), subclass/superclass (Unter-/Oberklasse), to delegate (weiterleiten), to wrap (umhüllen), behavior bzw. britisch behaviour (Verhalten), at runtime (zur Laufzeit), creational/structural/behavioral pattern.

Zum Üben findest Du zwei umfangreiche Aufgaben in den Übungsaufgaben zu Entwurfsmustern.

Leitfragen

  1. Was ist ein Entwurfsmuster, und welche Vorteile (und Nachteile) bringt seine Verwendung?

Ein Entwurfsmuster ist eine bewährte, allgemein beschriebene Lösungsschablone für ein wiederkehrendes Problem im Softwareentwurf – kein fertiger Code, sondern eine Beschreibung aus Name, Problem, Lösung (Klassen, Rollen, Zusammenarbeit) und Konsequenzen. Vorteile sind bewährte Lösungen, eine gemeinsame Fachsprache, bessere Wartbarkeit und Erweiterbarkeit, lose Kopplung, Wiederverwendbarkeit und schnellere Einarbeitung in fremden Code. Dafür entstehen mehr Klassen und Interfaces, zusätzliche Indirektion, und für kleine Probleme kann ein Muster überdimensioniert sein (siehe Abschnitt „Was ist ein Entwurfsmuster?“ und Abschnitt „Vorteile von Entwurfsmustern“).

  1. In welche drei Kategorien werden die GoF-Muster eingeteilt, und welche Muster gehören jeweils dazu?

Die GoF teilen ihre Muster nach dem Zweck ein: Erzeugungsmuster (z. B. Fabrikmethode, Abstrakte Fabrik, Singleton, Erbauer, Prototyp), Strukturmuster (z. B. Proxy, Dekorierer, Adapter, Fassade, Kompositum, Brücke) und Verhaltensmuster (z. B. Strategie, Zustand, Beobachter, Schablonenmethode, Befehl, Iterator). Sicher merken kannst Du Dir je zwei: Fabrikmethode und Singleton, Adapter und Fassade, Beobachter und Strategie (siehe Abschnitt „Kategorien von Entwurfsmustern“).

  1. Welche Rollen gibt es im Strategiemuster, und wie sieht die Methode aus, mit der der Kontext an die Strategie delegiert?

Das Strategiemuster hat drei Rollen: den Kontext, der eine Referenz auf die Strategie hält, die Strategie als Interface (oder abstrakte Klasse) mit der Algorithmusmethode und die konkreten Strategien, die das Interface realisieren. Der Kontext delegiert einfach an das gesetzte Objekt, z. B. Rückgabe strategie.dauerInMinuten(km) oder Rückgabe strategie.berechne(this), wenn die Strategie Daten des Kontexts braucht (siehe Abschnitt „Strategie (Strategy)“).

  1. Wie tauschst Du eine Strategie zur Laufzeit aus, und wie setzt Du eine Standardstrategie im Konstruktor?

Zur Laufzeit tauschst Du die Strategie über einen Setter aus, z. B. navi.setStrategie(new Fahrrad()), ohne den Kontext zu ändern. Die Standardstrategie weist Du im Konstruktor dem Attribut zu (this.strategie = new Auto()); ist der Kontext eine Unterklasse, rufst Du vorher den Basisklassenkonstruktor mit super(...) auf (siehe Abschnitt „Strategie (Strategy)“).

  1. Wie setzt Du beim Zustandsmuster den Anfangszustand, und wo steht die Bedingung für einen Zustandswechsel?

Den Anfangszustand setzt der Kontext in seinem Konstruktor, z. B. zustand := Entwurf.getInstanz(). Die Bedingung für einen Zustandswechsel steht nicht im Kontext, sondern als Kontrollstruktur in der Methode des jeweiligen Zustandsobjekts, das dann über d.setZustand(...) den Folgezustand setzt (siehe Abschnitt „Zustand (State)“).

  1. Warum sind die Konstruktoren der Zustandsklassen oft privat, und wie kommst Du dann an ein Zustandsobjekt?

Zustandsobjekte haben meist keine eigenen Daten, deshalb genügt je Zustandsklasse ein einziges Objekt, das als Singleton umgesetzt wird: Der private Konstruktor verhindert new von außen. Du bekommst das Objekt über die statische Zugriffsmethode getInstanz(), die das in einem statischen Attribut gespeicherte Objekt liefert und es beim ersten Aufruf erzeugt (siehe Abschnitt „Zustände als Singleton“).

  1. Was bedeutet Polymorphie am Beispiel des Attributs zustand?

Das Attribut zustand hat den statischen Typ Dokumentzustand, verweist zur Laufzeit aber auf ein Objekt vom Typ Entwurf, InPruefung oder Veroeffentlicht. Welche Implementierung bei zustand.weiter(this) ausgeführt wird, entscheidet sich erst zur Laufzeit am tatsächlichen Objekt (späte bzw. dynamische Bindung) – derselbe Aufruf bewirkt so je nach Zustand etwas anderes, ohne Fallunterscheidung im Kontext (siehe Abschnitt „Polymorphie am Zustandsmuster“).

  1. Woran unterscheidest Du Strategie und Zustand, obwohl die Klassendiagramme fast gleich aussehen?

Entscheidend sind Zweck und wer wechselt: Eine Strategie kapselt einen austauschbaren Algorithmus und wird meist von außen per Setter oder Konstruktor gesetzt, die konkreten Strategien kennen sich nicht. Ein Zustand kapselt das Verhalten in einem Zustand, den Wechsel lösen meist die Zustandsobjekte selbst aus, und ein Zustand kennt seine Folgezustände – typische Vorlage ist ein UML-Zustandsdiagramm (siehe Abschnitt „Strategie oder Zustand?“).

  1. Wozu dient ein Proxy, welche Arten gibt es, und warum muss der Client nicht geändert werden?

Ein Proxy ist ein Stellvertreter, der den Zugriff auf ein Objekt kontrolliert: Er realisiert dasselbe Interface wie das echte Objekt, führt eigene Logik aus und leitet den Aufruf weiter. Es gibt virtuelle Proxys (erzeugen erst bei Bedarf), Schutzproxys (prüfen Berechtigungen), Remote-Proxys (vertreten ein entferntes Objekt) und Caching-Proxys. Weil der Client nur mit dem gemeinsamen Interface arbeitet, merkt er nicht, ob er mit Proxy oder echtem Objekt spricht (siehe Abschnitt „Proxy (Stellvertreter)“).

  1. Wie sind die Klassen im Dekorierermuster verbunden, und wie umhüllst Du ein Objekt mit mehreren Dekorierern?

Konkrete Komponente und abstrakter Dekorierer realisieren dasselbe Interface, der Dekorierer hält zusätzlich eine Referenz auf ein Objekt dieses Interfaces (Aggregation, Raute am Dekorierer, Attribut protected), und die konkreten Dekorierer erben vom Dekorierer und reichen das umhüllte Objekt per super(inhalt) an den Basisklassenkonstruktor weiter. Zum Umhüllen erzeugst Du erst das Grundobjekt und verschachtelst dann Schicht für Schicht, z. B. new Zeitstempel(new Grossschrift(new Textmeldung("Server gestartet"))) (siehe Abschnitt „Dekorierer (Decorator)“).

  1. Worin unterscheiden sich Proxy, Dekorierer und Adapter?

Alle drei umhüllen ein Objekt und leiten Aufrufe weiter, der Zweck unterscheidet sie: Ein Proxy hat dieselbe Schnittstelle und kontrolliert den Zugriff (schützen, verzögern, entfernt aufrufen), ein Dekorierer hat ebenfalls dieselbe Schnittstelle, ergänzt aber Funktionen und ist beliebig verschachtelbar, ein Adapter bietet eine andere Schnittstelle an, um eine unpassende Schnittstelle anzupassen (siehe Abschnitt „Proxy, Dekorierer oder Adapter?“).

  1. Welches Muster passt, wenn viele Anzeigen bei einer Änderung aktualisiert werden sollen – und wie begründest Du das?

Hier passt das Beobachter-Muster: Das Subjekt benachrichtigt bei einer Änderung alle angemeldeten Beobachter (die Anzeigen) über ein gemeinsames Interface. Begründe, indem Du das Problem aus der Situation nennst (viele Anzeigen sollen automatisch aktualisiert werden) und zeigst, wie das Muster es löst (Benachrichtigung über ein Interface, lose Kopplung, neue Anzeigen ohne Änderung des Subjekts) (siehe Abschnitt „Welches Muster passt?“).


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Entwurfsmuster: Strategie, Zustand, Proxy und Dekorierer” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/entwurfsmuster veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/design-pattern/entwurfsmuster. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 28.10.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: