Decorators allow us to add behavior to objects without affecting other objects of the same class. The decorator pattern is a useful alternative to creating sub-classes. We will look at an example where we use subclassing to solve a problem, and then look at how decorator objects provide a better solution.

Imagine we have a Burger class with a cost method that returns 50.

class Burger def cost 50 end end

Now we need to represent burgers with an added layer of cheese, and the cost goes up by 10. The simplest approach is to create a BurgerWithCheese subclass that returns 60 in the cost method.

class BurgerWithCheese < Burger def cost 60 end end

Next, we need to represent a large burger that adds 15 to the cost of a normal burger. We can represent this using a LargeBurger subclass of Burger .

class LargeBurger < Burger def cost 65 end end

We could also have an ExtraLargeBurger which adds a further cost of 15 to our LargeBurger . If we were to consider that these burger types could be served with cheese, we would need to add LargeBurgerWithChese and ExtraLargeBurgerWithCheese subclasses.

With this approach, we end up with a total of 6 classes. Double that number if you want to represent these combinations with fries on the side.

Extending dynamically with modules

To simplify our code, we could use modules to dynamically add behavior to our Burger class. Let’s write CheeseBurger and LargeBurger modules for this.

module CheeseBurger def cost super + 10 end end module LargeBurger def cost super + 15 end end

Now we can extend our burger objects dynamically using the Object#extend method.

burger = Burger . new #=> cost = 50 burger . extend ( CheeseBurger ) #=> cost = 60 burger . extend ( LargeBurger ) #=> cost = 75

This is quite an improvement over our inheritance based implementation. Instead of having 6 classes, we just have one class and 3 modules. If we needed to add fries to the equation, we need just four modules instead of 12 classes.

Applying the decorator pattern

The modules based solution has simplified our code a great deal, but we could still improve upon it by using the decorator pattern. We could consider an ExtraLargeBurger as being formed by twice adding 15 to the cost of a Burger .

Our module based implementation doesn’t allow this. It would be tempting to call burger.extend(LargeBurger) twice to get an extra large burger. But when a module has already been used to extend an object, the second invokation of #extend has no effect.

If we were to continue using the same implementation, we would need to have an ExtraLargeBurger module that returns super + 30 as the cost. Instead, we could use decorator objects, that can be composed to build more complex objects. We start with a decorator called LargeBurger that is a wrapper around a Burger object.

class LargeBurger def initialize ( burger ) @burger = burger end def cost @burger . cost + 15 end end

Extra large burgers can now be created by using this wrapper twice on a Burger object.

burger = Burger . new large_burger = LargeBurger . new ( burger ) extra_large_burger = LargeBurger . new ( large_burger )

We can similarly represent cheese burgers using a BurgerWithCheese decorator. Using just three classes, we are now able to represent 6 types of burgers.

SimpleDelegator

Our decorator implementation has one disadvantage: if Burger has a #calories method, it will no longer be exposed after a decorator has been applied on it. To solve this problem, we will use Ruby’s SimpleDelegator class. First of all, we will implement a BurgerDecorator base class that all our decorator classes will inherit from.

class BurgerDecorator < SimpleDelegator def initialize ( burger ) @burger = burger super end end

When we call super in the #initialize method above, SimpleDelegator ensures that all the methods of the burger object are available on the decorated burger objects that we create.

When we create new decorator classes, we only need to inherit from BurgerDecorator and implement those methods that are new or different in those decorated objects.

Wrapping up

Decorators are a useful approach in cases where the objects have different types of behavior that can be combined in many ways. If we use inheritance in these cases, the number of subclasses can increase rapidly.

Since our decorated objects implement all the behavior of the original object, we can compose them to generate objects of any combination of behaviors.

This pattern can also be used to extract logic out of a complex class into other smaller classes. One common example is decorator classes that contain presentation logic in them.

Further reading