Design Patterns: Decorator Pattern

These notes were adapted from various readings detailed in the References section.


The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub classing for extending functionality.

We can extend functionality of objects at run-time. This allows us to avoid the need to overuse inheritance, and instead, favor object composition. There should be a way to give new responsibilities to a particular object without making any code changes to the underlying class.

Let’s imagine that you are now starting a beverage stand. Here is your system design:

class Beverage {
    protected description;
    
    public getDescription() { ... }
    public cost() { ... }
}

class HouseBlend extends Beverage {
    public cost() { ... }
}

class DarkRoast extends Beverage {
    public cost() { ... }
}

class Decaf extends Beverage {
    public cost() { ... }
}

class Espresso extends Beverage {
    public cost() { ... }
}

It is quite easy to notice here that every drink will be a subclass of Berage. However, there is one particular requirement that you anticipate which will be needed: What if each beverage may have different types of condiments applied to them, such as cream, or syrup?

The addition of condiments could mean that we would not only have a DarkRoast class, but also a DarkRoastWithCream, or DarkRoastWithSyrup, or even a DarkRoastWithCreamAndSyrup.

The number of classes per drink will increase exponentially, and become unmanageable! This is a maintenance nightmare!

Your partner in the business, Natalie suggests that we do not need all these classes. We can simply have instance variables storing condiments. Therefore, our DarkRoast class can still inherit Beverage and have flags set to see if that particular drink contains the condiment.

class Beverage {
    protected description;
    protected milk;
    protected soy;
    protected mocha;
    protected whip;
    
    public getDescription() { ... }
    public cost() { ... }
    
    public hasMilk() { ... }
    public setMilk() { ... }
    public hasSoy() { ... }
    public setSoy() { ... }
    public hasMocha() { ... }
    public setMocha() { ... }
    public hasWhip() { ... }
    public setWhip() { ... }
}

Now, all the cost method has to do is just calculate the cost of the drink based on the flags set within Beverage. However, you have some disagreement with Natalie in that this may not be the best approach.

A specific concern that you have is that if a new condiment is introduced, then Beverage will need to be modified to take into consideration, a new flag for that condiment. Not only that, the cost method will also need to be modified!

Also, what if you want to introduce a drink that is not coffee-based? Tea is certainly a Beverage, but some condiments for Tea may not make sense!

The power of composition is that we can extend behavior of an object at run-time as opposed to extending it at compile-time. This frees up the developer to write code to plug into the existing system as opposed to altering the existing system to make the new functionality work. The added benefit is that there are less chances in introducing new bugs, and side effects within the old system.

The Open-Closed Principle - Classes should be open for extension, but closed for modification.

The Decorator pattern allows us to have decorator objects acting as wrappers around a main object. Decorators will in addition, be sub-classes they will decorate, and it is valid that decorators can decorate each other in a recursive manner.

Here is what a system overview may look like utilizing the Decorator pattern:

class Component {
    methodA();
    methodB();
}

// ConcreteComponent will be component which can be decorated!
class ConcreteComponent extends Component {
    methodA();
    methodB();
}

// The intention is to have each Decorate wrap a Component by storing an instance
class Decorator extends Component {
    methodA();
    methodB();
}

// Do something specific
class ConcreteDecoratorA extends Decorator {
    protected Component wrappedComponent;

    methodA();
    methodB();
}

// Do another specific thing
class ConcreteDecoratorB extends Decorator {
    protected Component wrappedComponent;

    methodA();
    methodB();
}

A Decorator is a sub-class of the main base Component, and is the same like all other sub-classes inheriting from Component in that the same interface is implemented.

Any concreate decorator, ConcreteDecorator will also inherit from Decorator, and maintain an instance of Component that it decorates. From here, the client will actually use the ConcreateDecorator instance.

What the above system design enables us to do is to do clever things like wrap A over B!

const unwrapped = new ConcreteComponent();

// A will wrap Unwrapped
const wrappedA = new ConcreteDecoratorA(unwrapped);

// B will wrap A which in turn, wraps Unwrapped
const wrappedB = new ConcreteDecoratorB(wrappedA);

// Do stuff with wrappedB going forward
wrappedB.methodA();

Knowing this now, we can structure the system for our drinks like so:

// abstract so that it cannot be instantiated alone
abstract class Beverage() {
    protected description;
    
    public getDescription();
    
    public abstract cost();
}

class HouseBlend extends Beverage {
    public cost() { ... }
}

class DarkRoast extends Beverage {
    public cost() { ... }
}

class Decaf extends Beverage {
    public cost() { ... }
}

class Espresso extends Beverage {
    public cost() { ... }
}

// abstract so that it cannot be instantiated alone
abstract class CondimentDecorator extends Beverage {
    public abstract getDescription();
}

class Milk extends CondimentDecorator {
    Beverage beverage;
    
    public cost() { ... }
    public getDescription() { ... }
}

class Mocha extends CondimentDecorator {
    Beverage beverage;
    
    public cost() { ... }
    public getDescription() { ... }
}

class Soy extends CondimentDecorator {
    Beverage beverage;
    
    public cost() { ... }
    public getDescription() { ... }
}

class Whip extends CondimentDecorator {
    Beverage beverage;
    
    public cost() { ... }
    public getDescription() { ... }
}

Now a base beverage such as DarkRoast can easily return its cost. What about condiments? Well, all it needs to do is add its own cost with the cost returned by its wrapped beverage . For example:

class Mocha extends CondimentDecorator {
    Beverage beverage;
    
    public cost() {
        return 0.50 + beverage.cost();
    }
    public getDescription() { ... }
}

If the beverage happens to be another decorated object, its cost method will in turn, call its own beverage.cost() method in a recursive manner which will eventually terminate down to the base beverage of DarkRoast.

That is all there is to the decorator pattern!

References

  • Head First Design Patterns - Eric Freeman & Elisabeth Freeman - https://www.oreilly.com/library/view/head-first-design/0596007124/