1. Über das Decorator-Pattern
Das Decorator-Pattern ist ein Muster für eine Möglichkeit, Klassen dynamisch und sehr flexibel in ihren Eigenschaften und Funktionen zu erweitern. Kernprinzip ist es, dass es ein oder mehr sogenannte "konkrete Komponenten" gibt, sowie ebenfalls ein oder mehr Dekorierer (Decorators). Die konkreten Komponenten werden durch die Dekorierer erweitert. Mehrere Dekorierer lassen sich kombinieren bzw. "stapeln".
Häufig genannt wird das Kaffee-Beispiel: Ein Laden verkauft Kaffee mit verschiedenen Ergänzungsmöglichkeiten, wie Sahne, Karamell, extra Milch oder extra Zucker. Abhängig von den Zusätzen variieren zum Beispiel Preis, Kalorien und Zubereitungszeit des Kaffees. Es soll jede erdenkliche Kombination möglich sein. Dem Decorator-Pattern folgend ist nun der Kaffee die konkrete Komponente, während Sahne, Karamell, extra Milch und extra Zucker die Dekorierer sind. Bei der Instanziierung eines Dekorierers wird entweder ein Kaffee-Objekt oder ein anderer Dekorierer an den Konstruktor übergeben. So ergeben sich Kombinationen der Art Sahne(Sahne(Karamell(Kaffee))) oder etwa ExtraMilch(ExtraZucker(Kaffee)).
Sowohl die Dekorierer als auch die konkrete Komponente implementieren das selbe Interface, wodurch auf die Dekorierer die gleichen Funktionen angewendet werden können wie auf die konkrete Komponente. Würde etwa bei der Kombination ExtraMilch(ExtraZucker(Kaffee)) vom äußersten Dekorierer die Methode getPrice() aufgerufen werden, dann könnte er „0,25€ + getPrice() vom dekorierten Objekt” zurückgeben. Entsprechend würde er den Dekorierer ExtraZucker aufrufen, der nach dem selben Prinzip 0,10€ hinzuaddiert und den Preis vom Kaffee abfragt.
2. Beispiel mit Strings
In diesem Beispiel wird das Decorator-Pattern auf das „Säubern” von Strings angewendet, die von Benutzern übergeben wurden. Zum Beispiel sollen überflüssige Leerzeichen entfernt und HTML codiert werden.
Es gibt eine konkrete Komponente „SanitizeableString”, welche dekoriert werden soll. Dazu stehen die Dekorierer TrimmedString, EncodeHtml und OnlyAtoZ zur Verfügung. Ersterer führt einen trim() auf den dekorierten String aus, EncodeHtml encodiert die HTML-Zeichen via htmlentities() und OnlyAtoZ entfernt alle Zeichen, die nicht im Bereich von a bis z liegen. So würde etwa die Kombination TrimmedString(EncodeHtml(EncodeHtml(SanitizeableString))) den String aus SanitizeableString zuerst doppelt encodieren und dann trimmen.
<?php // Interface, das sowohl die Dekorierer als auch die konkrete Komponente einbinden // So brauchen sich Klassen "außerhalb" dieses Decorator-Patterns nicht darum kümmern, // das es Dekorierer gibt, sondern rufen (hier) einfach getString() auf bzw. prüfen auf // das Interface ISanitizeable interface ISanitizeable { public function getString(); } // Konkrete Komponente class SanitizeableString implements ISanitizeable { private $str; public function __construct($str) { $this->str = $str; } public function getString() { return $this->str; } } // Oberklasse aller Dekorierer abstract class SanitizedStringDecorator implements ISanitizeable { private $str; public function __construct(ISanitizeable $str) { $this->str = $str; } public function getString() { return $this->str->getString(); } } // Dekorierer, der ein trim() auf den String ausführt class TrimmedString extends SanitizedStringDecorator { public function getString() { return trim(parent::getString()); } } // Dekorierer, der HTML-Zeichen im String encoded class EncodeHtml extends SanitizedStringDecorator { public function getString() { return htmlentities(parent::getString(), ENT_QUOTES, 'UTF-8'); } } // Dekorierer, der alle Zeichen entfernt, die nicht im Bereich a bis z liegen class OnlyAtoZ extends SanitizedStringDecorator { public function getString() { return preg_replace('/[^a-zA-Z]/', '', parent::getString()); } } // Undekorierter String $str1 = new SanitizeableString('Ein Beispielsatz.'); var_dump($str1->getString()); // String mit trim() $str2 = new TrimmedString(new SanitizeableString(' Ein Beispielsatz. ')); var_dump($str2->getString()); // String mit codierten HTML-Zeichen $str3 = new EncodeHtml(new SanitizeableString(' Das ist ein <b>Test!</b> ')); var_dump($str3->getString()); // String mit codierten HTML-Zeichen, nur Zeichen aus dem Bereich a-z und trim() // aus " <> " // wird " <> " (EncodeHtml) // daraus wiederum " ltgt " (OnlyAtoZ) // und schließlich "ltgt" (TrimmedString) $str4 = new TrimmedString(new OnlyAtoZ(new EncodeHtml(new SanitizeableString(' <> ')))); var_dump($str4->getString()); ?>
string(17) "Ein Beispielsatz." string(17) "Ein Beispielsatz." string(42) " Das ist ein <b>Test!</b> " string(4) "ltgt"