SnapShooter Backups Server, Database, Application and Laravel Backups - Get fully protected with SnapShooter

Understanding Design Patterns - Decorator

Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

Coffee is a constant in almost everyone's day. The caffeine shot it gives to jump-start the day is something that most people cannot do without. Waking up to the smell of coffee brewing in the kitchen is enough to perk up anyone's day. The aroma, the taste, and the energy boost you get after a cup of coffee has made this dark drink an indispensible part of our mornings. No wonder why coffee shops like Starbucks and the Coffee Bean are doing so well in their business.

A cup of black coffee is served as a beverage without cream or milk, and it looks like this in code:

interface Beverage
{
    public function getName();
}
 
class Coffee implements Beverage
{
    public function getName()
    {
        return 'Coffee';
    }
}

However, black coffee is not really a popular choice because of its overpowering bitter taste. Most people prefer to have milk in their coffee; this not only subdues the strong flavor of the coffee but also gives a sweet mellow flavor to the drink.

To create it in code, what we would normally do is use subclassing. Inheritance seems to be a natural choice:

class CoffeeWithMilk extends Coffee
{
    public function getName()
    {
        return 'Coffee with milk';
    }
}

Now that we've added milk to our coffee, we realize that its not the only add-on we put in our drink. Some people like sugar, some prefer cream and sugar and some want milk and sugar. What if we need to create all the possible varieties? Supposing cream, milk, and sugar are the three available condiments, then there are six possible ways to make a cup of coffee. For every coffee variant, we will need to create a subclass:

class CoffeeWithMilk extends Coffee {}
class CoffeeWithCream extends Coffee {}
class CoffeeWithSugar extends Coffee {}
class CoffeeWithMilkAndCream extends Coffee {}
class CoffeeWithMilkAndSugar extends Coffee {}
class CoffeeWithCreamAndSugar extends Coffee {}

In reality, there are even more variants. Choices can be as simple as less milk or less sugar, but the impact of those variations need to be taken note of. Now when you think about the add-ons like cocoa powder, cinnamon and other flavors that can enhance coffee, this can be a maintenance nightmare.

We will need a better solution. This is when the Decorator Pattern comes in handy. We will create decorator classes that adds condiments to the coffee objects. First, let us create a decorator class that adds milk to a coffee:

class WithMilkDecorator implements Beverage
{
    private $_coffee = null;
 
    public function __construct(Coffee $coffee)
    {
        $this->_coffee = $coffee;
    }
 
    public function getName()
    {
        return $this->_coffee->getName().' with milk';
    }
}

Note that, the WithMilkDecorator class still implements Beverage interface as Coffee class does.

Let us also create two other decorators:

class WithSugarDecorator implements Beverage
{
    private $_coffee = null;
    public function __construct(Coffee $coffee)
    {
        $this->_coffee = $coffee;
    }
 
    public function getName()
    {
        return $this->_coffee->getName().' with sugar';
    }
}
 
class WithCreamDecorator implements Beverage
{
    private $_coffee = null;
    public function __construct(Coffee $coffee)
    {
        $this->_coffee = $coffee;
    }
 
    public function getName()
    {
        return $this->_coffee->getName().' with cream';
    }
}

Now, instead of having six subclasses of Coffee, we will have three decorators and they will achieve the same goal that we need. For example, to make a cup of coffee with milk and sugar:

$coffee = new Coffee();
$coffeeWithMilk = new WithMilkDecorator($coffee);
$coffeeWithMilkAndSugar = new WithSugarDecorator($coffeeWithMilk);
echo $coffeeWithMilkAndSugar->getName();

Clearly, by using the Decorator Pattern, we create less classes to maintain and avoid introducing bugs with more codes. What's more, Decorator Pattern adds additional feature to the wrapper class at run time, so it is flexible to add/remove features compared to inheritance via subclassing.

In our example, the Decorator Pattern attaches additional responsibilities (milk condiment, sugar condiment and cream condiment) to an object (Coffee object) dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

The end

Hopefully this simple tutorial helped you with your development. If you like our post, please follow us on Twitter and help spread the word. We need your support to continue. If you have questions or find our mistakes in above tutorial, do leave a comment below to let us know.