- 1 Section
- 10 Lessons
- unbegrenzt
Decorator
Du bestellst einen Kaffee. Ein Espresso kostet 2,00 €. Möchtest du Milch? +0,50 €. Karamell-Sirup? +0,40 €. Sahne obendrauf? +0,30 €. Du baust das Getränk Schicht für Schicht zusammen – und jeder „Aufsatz" verändert Geschmack und Preis. Das ist die Idee des Decorator-Patterns (Strukturmuster): du verpackst ein bestehendes Objekt in ein anderes, das dieselbe Schnittstelle hat und das Verhalten erweitert. Das Schöne: jede Erweiterung ist ihre eigene kleine Klasse, du kannst beliebig kombinieren, und du musst die ursprüngliche Klasse nicht ändern. Diese Lektion zeigt Decorator am Kaffee-Beispiel, am echten Java-Klassiker BufferedReader und vergleicht ihn mit dem klassischen Alternativ-Ansatz: Vererbung.
1) Das Problem: Vererbungs-Explosion
Wenn du jede Kombination als eigene Klasse modellierst, wird es schnell absurd: Espresso, EspressoMitMilch, EspressoMitSirup, EspressoMitMilchUndSirup, EspressoMitMilchUndSahne … bei 3 Aufsätzen sind das schon 8 Klassen (2³), bei 5 sind es 32. Außerdem: was, wenn du zwei Pumpstöße Sirup willst? Wieder neue Klasse. Decorator löst das, indem es jeden Aufsatz als Wrapper modelliert, der ein Getränk umschließt und selbst wieder ein Getränk ist. So entsteht eine Ketten-Struktur: Sahne(Sirup(Milch(Espresso()))). Mehr zu Vererbung und ihren Grenzen siehst du im OOP-Kurs.
2) Decorator live – Kaffee zusammenbauen
Wähl links die Aufsätze. Rechts siehst du die tatsächliche Objektkette, wie sie im Code aufgerufen würde, und den Preis, der sich durch rekursive Aufrufe ergibt. Jeder Decorator ruft beim verpackten Objekt getPreis() auf und addiert seinen eigenen Aufschlag:
new Sahne(new Sirup(new Espresso())) ergibt eine verschachtelte Kette – wenn getPreis() aufgerufen wird, fragt Sahne zuerst die innere Kette nach ihrem Preis (Rekursion) und addiert dann ihre 0,30 €.3) Code zum Beispiel
So sieht der Decorator in Java aus. Beachte: Milch, Sirup, Sahne erben alle vom selben abstrakten Decorator und halten eine Referenz auf das nächste Getränk. Wechsel zwischen den Tabs für die einzelnen Klassen:
public interface Getraenk { String getName(); double getPreis(); } public class Espresso implements Getraenk { public String getName() { return "Espresso"; } public double getPreis() { return 2.00; } }
4) Das echte Java-Beispiel: Streams
Decorator ist in Java überall – am bekanntesten in der I/O-Bibliothek. Wenn du eine Datei lesen willst, baust du dir eine Wrapper-Kette, in der jeder Wrapper eine zusätzliche Fähigkeit ergänzt: aus Datei lesen → puffern → als Zeichen interpretieren. Klick die einzelnen Stationen an, um zu sehen, was jeder Wrapper tut:
new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream("daten.txt"))))InputStream bzw. Reader – das gemeinsame Interface aller Decorators. Du kannst die Kette anders zusammenbauen: brauchst du keine Pufferung, lässt du BufferedInputStream einfach weg. Brauchst du Verschlüsselung? Setz einen CipherInputStream dazwischen.5) Wann Decorator, wann Vererbung?
Decorator und Vererbung lösen ähnliche Probleme – Verhalten erweitern – aber zu unterschiedlichen Zeitpunkten. Vererbung steht zur Übersetzungszeit fest: Du legst beim Schreiben des Codes fest, was eine Klasse kann. Decorator hingegen ist zur Laufzeit dynamisch: du kannst je nach Situation eine andere Kombination zusammenstecken.
| Vererbung | Decorator |
|---|---|
| Statisch – Klassenhierarchie steht fest | Dynamisch – Kombination zur Laufzeit |
| Eine Klasse pro Variante | Eine Klasse pro Aufsatz, beliebig kombinierbar |
| Bei 5 Aufsätzen: 32 Klassen | Bei 5 Aufsätzen: 5 Klassen + Basis |
| Reihenfolge nicht änderbar | Reihenfolge der Wrapper bestimmt Verhalten |
| Verstößt schnell gegen Open-Closed | Erfüllt Open-Closed: neuer Aufsatz = neue Klasse, alte bleiben unverändert |
Faustregel: wenn du Kombinationen hast und Reihenfolge eine Rolle spielen kann, ist Decorator die bessere Wahl. Mehr zum Thema Komposition statt Vererbung findest du bei den SOLID-Prinzipien – das ist das berühmte „Favor composition over inheritance"-Prinzip aus dem Gang-of-Four-Buch.
6) Decorator vs. Adapter vs. Proxy
Drei Strukturmuster, die strukturell ähnlich aussehen – ein Objekt umschließt ein anderes – aber unterschiedliche Absichten haben:
- Decorator: erweitert Funktionalität, das gleiche Interface bleibt. „Mein Wrapper kann mehr als das Original."
- Adapter: übersetzt zwischen Interfaces. Das Original hat Interface A, der Aufrufer erwartet Interface B – der Adapter übersetzt. „Mein Wrapper macht das Gleiche, sieht aber anders aus."
- Proxy: kontrolliert den Zugriff. Der Aufrufer denkt, er spricht mit dem Original, aber der Proxy steht dazwischen – z. B. für Caching, Lazy Loading oder Rechteprüfung. „Mein Wrapper sieht aus wie das Original, hat aber eine Wache eingebaut."
In der IHK-Prüfung kommen alle drei vor und werden gerne verwechselt. Merkhilfe: Decorator addiert, Adapter übersetzt, Proxy kontrolliert. Mehr zur Erkennung in Muster in Prüfungsaufgaben erkennen.
7) Praxis: wo Decorator dir täglich begegnet
Du benutzt Decorator wahrscheinlich, ohne es zu merken:
- Java I/O:
BufferedReader,InputStreamReader,GZIPInputStream,CipherInputStream– die ganzejava.io-Hierarchie. - Python: die
@decorator-Syntax (z. B.@property,@staticmethod,@app.route) ist eine Sprachversion desselben Konzepts – sie verpackt eine Funktion in eine andere. - Web-Middleware: in Express, Flask oder ASP.NET ist jede Middleware-Schicht ein Decorator über dem Request-Handler – Authentifizierung wrappt Logging wrappt CORS wrappt den eigentlichen Handler.
- UI-Frameworks: ScrollBar um ein TextField, Border um ein Panel – auch Wrappings, oft als Decorator umgesetzt.
Zusammenfassung
Decorator (Strukturmuster) erweitert ein Objekt um neue Funktionalität, indem es in ein anderes Objekt mit derselben Schnittstelle verpackt wird. Mehrere Decorator lassen sich ketten – jeder fügt seine Aufgabe hinzu und delegiert den Rest an das umschlossene Objekt. Vorteil gegenüber Vererbung: keine Klassenexplosion, beliebige Kombination, Reihenfolge wählbar, neue Aufsätze ändern bestehenden Code nicht. Bekanntestes Beispiel: BufferedReader und die ganze java.io-Familie. Nicht zu verwechseln mit Adapter (übersetzt Interfaces) und Proxy (kontrolliert Zugriff) – strukturell ähnlich, aber andere Absicht.
Verwandte Lektionen: Strategy · Factory · Vererbung · und mehrWeitere relevante LektionenWarum Entwurfsmuster?SOLID-PrinzipienAbstrakte Klassen & InterfacesPolymorphismusIteratoren & GeneratorenMuster in Prüfungsaufgaben erkennenSymmetrische Verschlüsselung
