Decorator-Muster
Das Decorator-Muster fügt einem Objekt dynamisch neues Verhalten hinzu, indem es in ein anderes Objekt mit derselben Schnittstelle eingewickelt wird. Verwende es, wenn du übergreifende Funktionen — Logging, Caching, Validierung — ohne Vererbung oder Verschmutzung des Basistyps hinzufügen willst.
Überblick
Das Decorator-Muster wickelt ein Objekt in ein anderes mit derselben Schnittstelle ein und fügt dadurch schichtweise Verhalten hinzu. Im Gegensatz zur Vererbung passiert die Dekoration zur Laufzeit und kann frei kombiniert werden: Derselbe Basisdienst kann mit einem Logger, dann mit einem Cache und dann mit einer Retry-Policy in beliebiger Reihenfolge umhüllt werden. Es ist die Grundlage für Dinge wie Express-Middleware und React Higher-Order Components.
Wann verwenden
- Du musst übergreifende Anliegen (Logging, Caching, Auth, Retry) hinzufügen, ohne die umhüllte Klasse zu ändern.
- Die Menge der Funktionen, die du kombinieren willst, ist offen und variiert je nach Aufrufstelle.
- Vererbung würde eine kombinatorische Klassenexplosion erzeugen (LoggedCachedRetryingService...).
Beispiel
interface DataService {
fetch(id: string): Promise<string>;
}
class HttpDataService implements DataService {
async fetch(id: string) { return `data:${id}`; }
}
class LoggingDecorator implements DataService {
constructor(private readonly inner: DataService) {}
async fetch(id: string) {
console.log(`fetch start ${id}`);
const result = await this.inner.fetch(id);
console.log(`fetch end ${id}`);
return result;
}
}
class CachingDecorator implements DataService {
private cache = new Map<string, string>();
constructor(private readonly inner: DataService) {}
async fetch(id: string) {
const cached = this.cache.get(id);
if (cached) return cached;
const result = await this.inner.fetch(id);
this.cache.set(id, result);
return result;
}
}
const service = new LoggingDecorator(
new CachingDecorator(new HttpDataService())
);Vorteile
- Verhalten zur Laufzeit komponieren, ohne die Originalklasse anzufassen.
- Open/Closed: Neue Decorators hinzufügen, ohne bestehenden Code zu ändern.
- Jedes Anliegen lebt in seiner eigenen Klasse — leicht isoliert testbar.
Nachteile
- Decorator-Stapel können schwer zu debuggen sein (welche Schicht hat dieses Verhalten hinzugefügt?).
- Die Reihenfolge der Komposition ist wichtig und leicht falsch zu wählen.
- Identität wird fragmentiert — decorated !== original, was Gleichheitsprüfungen brechen kann.