Wzorzec dekoratora
Część sekcji Programowanie obiektowe ścieżki PHP w Coddy. Lekcja 77 z 91.
Wzorzec Dekoratora to strukturalny wzorzec projektowy, który pozwala dynamicznie dodawać nowe zachowania do obiektów przez opakowywanie ich w specjalne obiekty opakowujące zwane dekoratorami. W przeciwieństwie do dziedziczenia, które dodaje zachowanie w czasie kompilacji, dekoratory dodają zachowanie w czasie wykonywania.
Wyobraź sobie kawiarnię. Zaczynasz od podstawowej kawy, a potem dodajesz dodatki, takie jak mleko, cukier lub bita śmietana.
Każdy dodatek otacza oryginalną kawę oraz rozszerza jej opis i zwiększa jej koszt. Kluczowe jest to, że zarówno kawa bazowa, jak i dekoratory korzystają z tego samego interfejsu.
<?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;
}
}
Dekoratory implementują ten sam interfejs i opakowują inny obiekt Coffee:
<?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();
Wynik:
Simple Coffee, Milk, Sugar = $2.75Każdy dekorator opakowuje poprzedni obiekt, tworząc łańcuch. Ten wzorzec pozwala łączyć zachowania w dowolnej kolejności i liczbie, bez tworzenia klasy dla każdej możliwej kombinacji. Chcesz podwójną porcję mleka? Po prostu opakuj go dwa razy.
Wyzwanie
ŁatwyZbudujmy system zamawiania pizzy z użyciem wzorca Dekoratora. Zaczniesz od podstawowej pizzy i będziesz dynamicznie dodawać dodatki: każdy dodatek opakowuje pizzę i rozszerza jej opis oraz cenę, tak jak w przykładzie z kawą z lekcji.
Rozmieścisz swój kod w czterech plikach:
Pizza.php: Zdefiniuj interfejsPizza, który określa kontrakt dla wszystkich pizz i dodatków. Powinien mieć dwie metody:getDescription(): stringigetPrice(): float.BasePizza.php: Dołącz interfejs Pizza i utwórz klasęMargheritaPizza, która go implementuje. To twoja podstawowa pizza z opisem"Margherita"i ceną8.00.Toppings.php: Dołącz interfejs Pizza i utwórz trzy klasy dekoratorów, które go implementują. Każdy dekorator opakowuje obiektPizza(przyjmuje go w konstruktorze) i rozszerza zarówno opis, jak i cenę:CheeseTopping: Dodaje", Extra Cheese"do opisu i1.50do cenyPepperoniTopping: Dodaje", Pepperoni"do opisu i2.00do cenyMushroomTopping: Dodaje", Mushrooms"do opisu i1.25do ceny
main.php: Dołącz pliki BasePizza i Toppings. Otrzymasz jedno wejście: listę dodatków do dodania, oddzielonych przecinkami (np."cheese,pepperoni"lub"mushroom,cheese,cheese").Zacznij od
MargheritaPizza. Dla każdego dodatku z wejścia opakuj pizzę odpowiednim dekoratorem:"cheese"opakowuje ją za pomocąCheeseTopping"pepperoni"opakowuje ją za pomocąPepperoniTopping"mushroom"opakowuje ją za pomocąMushroomTopping
Po dodaniu wszystkich dodatków wypisz opis końcowej pizzy i jej cenę w tym formacie:
[description] = $[price]Sformatuj cenę do dwóch miejsc po przecinku.
Zwróć uwagę, że ten sam dodatek można zastosować wiele razy: zamówienie podwójnej porcji sera oznacza po prostu dwukrotne opakowanie pizzy za pomocą CheeseTopping. Na tym polega elastyczność wzorca Dekoratora!
Spróbuj swoich sił
<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';
// Wczytaj dane wejściowe – listę dodatków oddzielonych przecinkami
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);
// Zacznij od podstawowej MargheritaPizza
$pizza = new MargheritaPizza();
// TODO: Przejdź przez każdy dodatek i owiń pizzę odpowiednim dekoratorem
// - "cheese" -> owiń dekoratorem CheeseTopping
// - "pepperoni" -> owiń dekoratorem PepperoniTopping
// - "mushroom" -> owiń dekoratorem MushroomTopping
// TODO: Wypisz wynik w formacie: [description] = $[price]
// Sformatuj cenę do 2 miejsc po przecinku za pomocą number_format()
?>Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe $thisMetodyWłaściwościKonstruktor (__construct)Destruktor (__destruct)Powtórka — prosty kalkulator4Dziedziczenie
Podstawy dziedziczeniaSłowo kluczowe parent::Przesłanianie metodSłowo kluczowe finalKlasy abstrakcyjnePowtórzenie – hierarchia pracowników7Enkapsulacja
Public, protected, privateModyfikatory dostępu w szczegółachGettery i setteryUkrywanie informacjiPromocja parametrów konstruktora (8.0)Podsumowanie — system ewidencji studentów10Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieWstrzykiwanie zależnościKlasy anonimoweEnumy (PHP 8.1)Włókna (PHP 8.1)Dogłębne omówienie klonowania obiektówGeneratory i iteratory13Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytuWzorzec repozytorium2Przestrzenie nazw i autoloading
Wprowadzenie do przestrzeni nazwSłowo kluczowe useStandard autoloadingu PSR-4Autoloader ComposeraPodsumowanie – uporządkowany projekt5Interfejsy i kontrakty
Wprowadzenie do interfejsówImplementowanie interfejsówImplementowanie wielu interfejsówInterfejs a klasa abstrakcyjnaPodpowiadanie typów za pomocą interfejsówPodsumowanie — kalkulator kształtów8Metody magiczne
Wprowadzenie do metod magicznych__toString i __debugInfo__get, __set, __isset, __unset__call i __callStatic__clone i klonowanie obiektów__serialize i __unserializePodsumowanie — własna kolekcja11System typów i obsługa błędów
Deklaracje typówTypy nullableTypy sumy i przecięciaKlasy wyjątkówNiestandardowa hierarchia wyjątkówTry, Catch, FinallyPodsumowanie — walidator formularzy14Projekt: Zarządzanie biblioteką
Przegląd projektuKlasy książki i użytkownika3Właściwości klas
Właściwości instancji a właściwości statyczneStałe w klasachMetody i właściwości statyczneWłaściwości prywatne i chronioneWłaściwości tylko do odczytu (PHP 8.1)Podsumowanie — menedżer kont bankowych6Polimorfizm
Ponowne omówienie nadpisywania metodPolimorfizm za pomocą interfejsówPodpowiedzi typów i typy sumyPóźne wiązanie statycznePodsumowanie — procesor płatności9Cechy
Wprowadzenie do cechKorzystanie z wielu cechRozwiązywanie konfliktów cechMetody abstrakcyjne w cechachCechy a dziedziczenie12Wzorce projektowe, część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec ObserverWzorzec StrategyPoćwicz samodzielnie: Kompilator PHP online