Easy Python Patterns: The decorator pattern

Introduction

The Decorator pattern can be used to dynamically alter or add functionality to existing classes. This pattern is oftern more efficient than subclassing because it relieves us of the need of defining a new object.

The UML diagram looks like this:

To decorate a certain class the following steps must be taken:

  1. Construct a subclass, or in our case an implementation of the interface you want to decorate.
  2. In the Decorator class, make sure you add a reference to the original class.
  3. Also in constructor of the Decorator class, make sure to pass a reference to the original class to the constructor.
  4. Where needed, forward all requests to methods in the original class.
  5. And where needed, change the behaviour of the rest.

This all works because both the Decorator and the original class implement the same interface.

Implementation

We will start with Component we want to decorate:

class Component:
    def operation(self) -> str:
        pass

For our very simplified example, the Component class functions as an interface or a trait.

Next we define the ConcreteComponent, where the operation() method just returns a string:

class ConcreteComponent(Component):
    def operation(self) -> str:
        return "ConcreteComponent.operation"

In the following code we define a ConcreteDecoratorA which wraps a Component class and a ConcreteDecoratorB which wraps the previous decorator class. That way, once we call the operation() method on the ConcreteDecoratorB, the call eventually goes down all the way to the ConcreteComponent.

class ConcreteDecoratorA(Component):
    _component: Component = None

    def __init__(self, new_component: Component):
        self._component = new_component

    def operation(self) -> str:
        return f"ConcreteDecoratorA.operation({self._component.operation()})"


class ConcreteDecoratorB(Component):
    _component: Component = None

    def __init__(self, new_component: Component):
        self._component = new_component

    def operation(self) -> str:
        return f"ConcreteDecoratorB.operation({self._component.operation()})"

Note that we could pass either a ConcreteComponent or a decorator class to the constructors of the decorators, because all ultimately derive from the Component class. This is how we can ‘stack’ decorators on top of other classes without modifying the code.

Time to test

We can now test our assumptions:

if __name__ == "__main__":
    component: Component = ConcreteComponent()
    decorator_a: ConcreteDecoratorA = ConcreteDecoratorA(component)
    decorator_b: ConcreteDecoratorB = ConcreteDecoratorB(decorator_a)
    print(decorator_b.operation())

A line by line explanation:

  1. We create a ConcreteComponent object
  2. That is class gets wrapped in a ConcreteDecoratorA object
  3. In turn, that gets wrapped in a ConcreteDecoratorB object
  4. Finally we call the operation() method on the ConcreteDecoratorB object. Since the ConcreteComponentConcreteDecoratorA, and ConcreteDecoratorB all inhererit from Component, the operation() method can be called on all three.

See how type hints make the code much more readable. Editors like PyCharm also issue warnings if you violate the type hints.

Conclusion

As you can see, the Decorator pattern is an easy way to either dynamically or statically change or add behaviour to objects. In many cases it is more flexible than subclassing. In some cases it can also prevent an explosion of subclasses.

Implementing this pattern in Python was almost ridiculously easy, to be honest. Putting in the type-hints and defining the class attributes, like _component also made the code more elegant and readable.

Use cases for this pattern could include for example be adding behaviour to flyweight objects or logging.

Leave a Reply

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