The Decorator Pattern

1. Definition

The decorator pattern allows you to attach additional responsibilities to an existing object dynamically.

2. Case study

2.1 The Problem

You’re the owner of a pizza store.

Because nowadays everyone uses the internet to order pizzas, you decided to have a web application for taking orders from customers.

In your app, each pizza was made as a class.

abstract class Pizza
{
    protected $price;
    public function getPrice() { return $this->price; }
}

class SeafoodPizza extends Pizza
{
    public function __construct() 
    {
        $this->price = 20;
    }
}

class BBQPizza extends Pizza
{
    public function __construct()
    {
        $this->price = 22;
    }
}

class HawaiianPizza extends Pizza
{
    //... and so on.
}

Soon you realized that the customer had to be able to add toppings. This means that the price of a pizza changes if the customer wants to add Cheese, Onion, Pineapple etc.

So you made changes on the pizza classes.

abstract class Pizza
{
    protected $price;

    //The prices of toppings
    protected $cheese;
    protected $spinach;

    //Boolean
    protected $hasCheese;
    protected $hasSpinach;

    public function __construct()
    {
        $this->cheese = 7;
        $this->spinach  = 8;
    }

    public function hasCheese() { return $this->hasCheese; }
    public function hasSpinach() { return $this->hasSpinach; }

    public function setCheese($set = true) {
        return $this->hasCheese = $set;
    }
    public function setSpinach($set = true) {
        return $this->hasSpinach = $set;
    }

    //Where is getPrice()?
}

As you can see, it’s determined by boolean properties whether a pizza has a topping.

Then, how does each pizza return the total price?

abstract class Pizza
{
    //cont.

    public function getPrice() {
        $cost = $this->price;

        if($this->hasCheese)
        {
            $cost += $this->cheese;
        }

        if($this->hasSpinach)
        {
            $cost += $this->spinach;
        }

        return $cost;
    }
}

Then, you updated the subclasses, so that they inherit all the properties and functions.

class SeafoodPizza extends Pizza
{
    public function __construct()
    {
        parent::__construct();
        $this->price = 20;
    }
}

class BBQPizza extends Pizza
{
    public function __construct()
    {
        parent::__construct();
        $this->price = 22;
    }
}

In this way, you allowed all the subclasses (SeafoodPizza, BBQPizzaHawaiianPizza etc) to have toppings and change their price accordingly with minimum effort.

$s = new SeafoodPizza();
$s->getPrice()
// 20
$s->setCheese()
$s->getPrice()
// 27
$s->setSpinach()
$s->getPrice()
// 35

Nevertheless, this approach has some potential drawbacks.

What if…

A. the customer wants to put double cheese?

B. you want to exclude pineapple from the topping list for BBQ pizza?

C. you need to use Mozzarella cheese onto only seafood pizza but Cheddar cheese onto other pizzas?

In other words, extending a class through inheritance (i.e. superclass) may …

  1. limit the number of behaviours you want to add into an existing class
  2. lead to undesirable consequences on some of the subclasses
  3. force developers to change existing code

Among them, the most notable issue is probably the 3rd one, since this goes against the open-closed principle.

This principle states that…

classes should be open for extention, but closed for modification

You’ve spent a lot of time in coding to make sure that your app is bug-free, so you don’t want to take a risk of breaking it, right?

Let’s see how we can handle those issues with the Decorator Pattern.

2.2 The solution

1. Remove the toppings from the pizza classes, and make getPrice() abstract.

abstract class Pizza
{
    protected $price;
    abstract public function getPrice();
}

class SeafoodPizza extends Pizza
{
    public function __construct()
    {
        $this->price = 20;
    }

    public function getPrice() { return $this->price; }
}

class BBQPizza extends Pizza
{
    public function __construct()
    {
        $this->price = 22;
    }

    public function getPrice() { return $this->price; }
}

2. Create the Decorator class.

abstract class ToppingDecorator extends Pizza { }

3. Implement each topping.


class Cheese extends ToppingDecorator
{
    private $pizza;
    
    public function __construct($pizza)
    {
        $this->pizza = $pizza;
        $this->price = 7;
    }

    public function getPrice() {
        return $this->pizza->getPrice() + $this->price;
    }
}
//Do the same for Onion, Pineapple etc

Now we are ready to go.

$s = new SeafoodPizza();
$s->getPrice();
// 20
$s = new Cheese($s);
$s->getPrice();
// 27
$s = new Cheese($s);
$s->getPrice();
//34
$s = new Onion($s);
//and so on

2.3 The outcome

Having applied the Decorator Pattern,  now you’re able to add new behaviours to the pizza classes without altering the existing code.

On top of that, now you can add the same changes (adding cheese multiple times) onto any of the subclasses.

Last but not least, you can select which subclass to extend, which is often impossible with inheritance.

3. The structure

You can add as many concrete components/decorators as you want.

A. Component (Pizza)

This is the class which all the concrete classes (pizzas) are extended from.

Note that the component can be an interface, although I used an abstract superclass in the example above.

B. Concrete Component(s) (SeafoodPizza, BBQPizza etc)

They are the classes you can add new behaviours at runtime.

C. Decorator (ToppingDecorator)

This implements the same interface/abstract class as the component class so that this can be in the position of the component.

Note that the decorator isn’t meant to inherit anything.

D. Concrete Decorator(s) (Cheese, Onion etc)

They implement/override the component’s method(s).

In our case, what the concrete decorators (toppings) did was to compute the total price by adding their price to a pizza (concrete component).

Of course, you can have concrete decorators do various things other than just adding numeric values.

4. When to use this pattern

When you need to extend a behaviour (typically, it’s a function) of an object class without affecting the original purpose.

In particular, you may well consider the Decorator Pattern if you are trying to do so through inheritance, because inheritance can’t be altered at runtime and might be irrelevant to some of the subclasses.

5. Potential drawbacks

Introducing the Decorator pattern can lead to having too many subclasses, which may end up a design which isn’t easy for other developers to comprehend.

Also, creating multi-layer decorators can make it highly complicated to instantiate an object.

Leave a Reply

Your email address will not be published. Required fields are marked *