Strukturmuster
Decorator
Eine Klasse soll zusätzlich etwas tun — protokollieren, zwischenspeichern, bei Fehlern erneut versuchen —, aber das Zusätzliche gehört fachlich nicht zu ihr. Der Decorator implementiert dieselbe Schnittstelle, hält das ursprüngliche Objekt und legt sein Verhalten darum herum.
Struktur
Gleiche Schnittstelle außen wie innen — deshalb lassen sich Decorator stapeln, und der Aufrufer merkt nichts davon.
interface Kursabfrage
{
public function kurs(string $waehrung): Betrag;
}
final class GecachteKursabfrage implements Kursabfrage
{
private array $cache = [];
public function __construct(private readonly Kursabfrage $inner) {}
public function kurs(string $waehrung): Betrag
{
return $this->cache[$waehrung] ??= $this->inner->kurs($waehrung);
}
}
// Zusammensetzen an einer Stelle — die Fachklasse weiß von alldem nichts.
$abfrage = new GecachteKursabfrage(new ProtokollierteKursabfrage(new ApiKursabfrage()));
Praxisnotiz
Für Querschnittsthemen ist der Decorator mein Standardwerkzeug. Cache, Protokollierung, Metriken, Wiederholversuche, Zeitmessung: Alles davon lässt sich umhüllen, und die Fachklasse bleibt frei davon. Der praktische Vorteil zeigt sich beim Entfernen — eine Zeile in der Verdrahtung, und die Zusatzschicht ist weg. Bei eingebautem Verhalten ist dasselbe eine Operation am offenen Herzen.
Der Preis ist die Nachvollziehbarkeit. Ein dreifach umhülltes Objekt liest sich im Stacktrace nicht mehr wie der Code, den man vor sich hat, und beim Debuggen läuft man durch Schichten, die man in der Klasse nicht sieht. Ab etwa drei Lagen wird das spürbar. Wichtig ist deshalb, dass die Verdrahtung an einer Stelle steht und man dort in einem Blick sieht, was tatsächlich um was gelegt ist.
Zweiter Punkt aus Reviews: Decorator müssen sich an den Vertrag halten. Ein Cache, der Ausnahmen schluckt, oder ein Retry, der Idempotenz voraussetzt, wo keine besteht, verändert das Verhalten — und zwar unsichtbar, weil der Typ derselbe bleibt. Das sind die Fehler, die spät und an falscher Stelle auffallen.
Viele Frameworks bieten dafür AOP oder Interceptoren. Das ist bequem, aber unsichtbarer als ein explizit verdrahteter Decorator. Ich nehme im Zweifel die sichtbare Variante.
Wann es nicht passt
-
Wenn die Erweiterung fachlich zum Objekt gehört. Dann ist sie kein Zusatz, sondern Teil der Aufgabe.
-
Wenn die Schnittstelle breit ist: Jede Methode muss durchgereicht werden, und jeder neue Vertragsbestandteil trifft alle Decorator.
-
Wenn nur eine einzige Variante existiert und absehbar bleibt — dann genügt es, das Verhalten an Ort und Stelle zu schreiben.