Dekorator
Typ: Strukturalny
Wyobraźmy sobie sytuację, gdzie kombinacja wielu dodatków do bazowej klasy spowodowałaby rozlew klas dziedziczących, które programista musiałby zaimplementować w aplikacji, na przykład: PizzaWithPepperoniAndTabasco extends Pizza, PizzaWithPepperoniAndMushrooms extends Pizza i tak dalej. Można sobie łatwo wyobrazić jak wiele typowa pizzeria może stworzyć kombinacji tego smacznego placka. Z pomocą przychodzi wzorzec dekorator, który umożliwia nam rozszerzanie funkcjonalności obiektu w czasie pracy aplikacji.
Przykładowa implementacja
interface SomeFancyInterface
{
public function getInfo();
}
class BasicObject implements SomeFancyInterface
{
public function getInfo()
{
return "I'm basic object";
}
}
class Decorator implements SomeFancyInterface
{
private $object;
public function __construct(SomeFancyInterface $object)
{
$this->object = $object;
}
public function getInfo()
{
return $this->object->getInfo() . " decorated!";
}
}
$object = new BasicObject();
$decoratedObject = new Decorator($object);
echo $decoratedObject->getInfo() . PHP_EOL; // I'm basic object decorated!
Kiedy używać
Dekorator jest niezwykle użyteczny w momencie kiedy naturalnym zachowaniem pewnego obiektu jest jego podatność na rozszerzenia. Można wyobrazić sobie wiele przypadków użycia, jak na przykład proces zakupu samochodu (dodwanie nowych opcji, jak nawigacja, przyciemniane szyby), wspomniana wyżej pizzeria, polisa ubezpieczeniowa itp. Zaprojektujmy zatem banalny model dla sprzedaży telewizji kablowej, skupmy się na ofercie.
Przykład użycia
Specyfikacja
- Mamy podstawową ofertę
- Klient może do oferty dodać opcje: pakiet sportowy, edukacyjny i filmowy
- Zbudujmy finalną ofertę za pomocą dekoratorów
Implementacja
interface Offer
{
public function getPrice();
}
final class BaseOffer implements Offer
{
public function getPrice()
{
return 40;
}
}
final class SportDecorator implements Offer
{
private $offer;
public function __construct(Offer $offer)
{
$this->offer = $offer;
}
public function getPrice()
{
return $this->offer->getPrice() + 20;
}
}
final class EducationDecorator implements Offer
{
private $offer;
public function __construct(Offer $offer)
{
$this->offer = $offer;
}
public function getPrice()
{
return $this->offer->getPrice() + 10;
}
}
final class CinemaDecorator implements Offer
{
private $offer;
public function __construct(Offer $offer)
{
$this->offer = $offer;
}
public function getPrice()
{
return $this->offer->getPrice() + 30;
}
}
$offer = new BaseOffer();
$offerWithSport = new SportDecorator($offer);
$offerWithSportAndCinema = new CinemaDecorator(new SportDecorator($offer));
$fullOffer = new CinemaDecorator(new SportDecorator(new EducationDecorator($offer)));
Omówienie
Wprowadziliśmy wspólny interfejs dla podstawowej klasy jak i klas dekoratorów. Z racji tego, że każdy obiekt dekorowany będzie spójny interfejsem z obiektem bazowym, możemy swobodnie dekorować obiekty zmieniając ich właściwości. Powyższy przykład został zaimplementowany przy użyciu interfejsów, ale równie dobrze można go zaimplementować przy użyciu klas abstrakcyjnych.