Dekorierermuster
Teil des Abschnitts Objektorientierte Programmierung der PHP-Journey von Coddy. Lektion 77 von 91.
Das Decorator Pattern ist ein strukturelles Entwurfsmuster, mit dem du Objekten dynamisch neue Verhaltensweisen hinzufügen kannst, indem du sie in spezielle Wrapper-Objekte einhüllst, die als Decorators bezeichnet werden. Im Gegensatz zur Vererbung, die Verhalten zur Kompilierungszeit hinzufügt, fügen Decorators Verhalten zur Laufzeit hinzu.
Stell dir ein Café vor. Du beginnst mit einem einfachen Kaffee und fügst dann Extras wie Milch, Zucker oder Schlagsahne hinzu.
Jede Ergänzung umschließt den ursprünglichen Kaffee und erweitert seine Beschreibung und Kosten. Die entscheidende Erkenntnis ist, dass sowohl der Basiskaffee als auch die Dekorierer dieselbe Schnittstelle verwenden.
<?php
interface Coffee {
public function getDescription(): string;
public function getCost(): float;
}
class SimpleCoffee implements Coffee {
public function getDescription(): string {
return "Simple Coffee";
}
public function getCost(): float {
return 2.00;
}
}
Dekoratoren implementieren dieselbe Schnittstelle und umschließen ein anderes Coffee-Objekt:
<?php
class MilkDecorator implements Coffee {
public function __construct(private Coffee $coffee) {}
public function getDescription(): string {
return $this->coffee->getDescription() . ", Milk";
}
public function getCost(): float {
return $this->coffee->getCost() + 0.50;
}
}
class SugarDecorator implements Coffee {
public function __construct(private Coffee $coffee) {}
public function getDescription(): string {
return $this->coffee->getDescription() . ", Sugar";
}
public function getCost(): float {
return $this->coffee->getCost() + 0.25;
}
}
$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);
echo $coffee->getDescription() . " = $" . $coffee->getCost();
Output:
Simple Coffee, Milk, Sugar = $2.75Jeder Decorator umschließt das vorherige Objekt und bildet so eine Kette. Das Muster ermöglicht es dir, Verhaltensweisen in beliebiger Reihenfolge und Anzahl zu kombinieren, ohne für jede mögliche Kombination eine Klasse erstellen zu müssen. Du brauchst doppelte Milch? Umschließe es einfach zweimal.
Aufgabe
EinfachLass uns ein Pizza-Bestellsystem mit dem Decorator-Muster erstellen. Du beginnst mit einer einfachen Pizza und fügst dynamisch Beläge hinzu: Jeder Belag umschließt die Pizza und ergänzt ihre Beschreibung und ihren Preis, genau wie im Kaffee-Beispiel aus der Lektion.
Du organisierst deinen Code über vier Dateien:
Pizza.php: Definiere einPizza-Interface, das den Vertrag für alle Pizzen und Beläge festlegt. Es sollte zwei Methoden haben:getDescription(): stringundgetPrice(): float.BasePizza.php: Binde das Pizza-Interface ein und erstelle eineMargheritaPizza-Klasse, die dieses implementiert. Dies ist deine Basispizza mit der Beschreibung"Margherita"und einem Preis von8.00.Toppings.php: Binde das Pizza-Interface ein und erstelle drei Decorator-Klassen, die dieses implementieren. Jeder Decorator umschließt einPizza-Objekt (übergebe es im Konstruktor) und erweitert sowohl die Beschreibung als auch den Preis:CheeseTopping: Fügt der Beschreibung", Extra Cheese"und dem Preis1.50hinzuPepperoniTopping: Fügt der Beschreibung", Pepperoni"und dem Preis2.00hinzuMushroomTopping: Fügt der Beschreibung", Mushrooms"und dem Preis1.25hinzu
main.php: Binde die Dateien BasePizza und Toppings ein. Du erhältst eine Eingabe: eine durch Kommas getrennte Liste von Belägen, die hinzugefügt werden sollen (z. B."cheese,pepperoni"oder"mushroom,cheese,cheese").Beginne mit einer
MargheritaPizza. Umschließe deine Pizza für jeden Belag in der Eingabe mit dem passenden Decorator:"cheese"umschließt sie mitCheeseTopping"pepperoni"umschließt sie mitPepperoniTopping"mushroom"umschließt sie mitMushroomTopping
Gib nach dem Anwenden aller Beläge die Beschreibung und den Preis der fertigen Pizza in diesem Format aus:
[description] = $[price]Formatiere den Preis mit zwei Dezimalstellen.
Beachte, dass derselbe Belag mehrmals angewendet werden kann: Doppelter Käse bedeutet einfach, dass die Pizza zweimal mit CheeseTopping umschlossen wird. Diese Flexibilität ist die Stärke des Decorator-Musters!
Probier es selbst
<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';
// Eingabe lesen - kommagetrennte Liste von Belägen
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);
// Beginne mit einer Basis-MargheritaPizza
$pizza = new MargheritaPizza();
// TODO: Durchlaufe jeden Belag und umhülle die Pizza mit dem entsprechenden Decorator
// - "cheese" -> mit CheeseTopping umhüllen
// - "pepperoni" -> mit PepperoniTopping umhüllen
// - "mushroom" -> mit MushroomTopping umhüllen
// TODO: Gib das Ergebnis im Format aus: [description] = $[price]
// Formatiere den Preis auf 2 Dezimalstellen mit number_format()
?>Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienEinführung in OOPKlassen vs. ObjekteDas Schlüsselwort $thisMethodenEigenschaftenKonstruktor (__construct)Destruktor (__destruct)Rückblick – Einfacher Taschenrechner4Vererbung
Grundlegende VererbungDas Schlüsselwort parent::MethodenüberschreibungDas Schlüsselwort finalAbstrakte KlassenRückblick – Mitarbeiterhierarchie7Kapselung
Öffentlich, geschützt, privatZugriffsmodifikatoren im DetailGetter und SetterInformationsverbergungKonstruktor-Promotion (8.0)Wiederholung – System zur Verwaltung von Schülerdatensätzen10Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungDependency InjectionAnonyme KlassenEnums (PHP 8.1)Fibers (PHP 8.1)Objektklonen im DetailGeneratoren & Iteratoren13Entwurfsmuster Teil 2
BefehlsmusterAdaptermusterDekorierermusterSchablonenmethodenmusterZustandsmusterKompositummusterRepository-Muster2Namespaces & Autoloading
Einführung in NamespacesDas use-SchlüsselwortPSR-4-Autoloading-StandardComposer-AutoloaderRückblick – Organisiertes Projekt5Schnittstellen & Verträge
Einführung in SchnittstellenSchnittstellen implementierenMehrere Schnittstellen implementierenSchnittstelle vs. abstrakte KlasseTyp-Hinweise mit SchnittstellenRückblick – Formrechner8Magische Methoden
Einführung in magische Methoden__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Objektklonen__serialize & __unserializeRückblick – Benutzerdefinierte Sammlung11Typsystem & Fehlerbehandlung
TypdeklarationenNullable-TypenUnion- & Intersection-TypenException-KlassenBenutzerdefinierte Exception-HierarchieTry, Catch, FinallyRückblick – Formularvalidator14Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen3Klassen-Eigenschaften
Instanz- vs. statische EigenschaftenKonstanten in KlassenStatische Methoden & EigenschaftenPrivate & geschützte EigenschaftenSchreibgeschützte Eigenschaften (PHP 8.1)Zusammenfassung – Bankkonto-Manager6Polymorphie
Methodenüberschreibung erneut betrachtetPolymorphie über SchnittstellenTyp-Hinweise & Union-TypenSpätes statisches BindenRückblick – Zahlungsabwickler9Traits
Einführung in TraitsMehrere Traits verwendenAuflösung von Trait-KonfliktenAbstrakte Methoden in TraitsTraits vs. Vererbung12Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterObserver-MusterStrategy-Muster15Abschließende Herausforderungen
E-Learning-PlattformBankingsystemEntwicklung einer SpielfigurFahrzeugvermietungÜbe selbstständig: Online-PHP-Compiler