Skip to content

Decorator Design Pattern

Video Lecture

Section Video Links
Decorator Overview Decorator Overview Decorator Overview Decorator Overview 
Decorator Use Case Decorator Use Case Decorator Use Case Decorator Use Case 
_str_ Dunder Method __str__ Dunder Method __str__ Dunder Method __str__ Dunder Method 
Python getattr() Method getattr() Method getattr() Method getattr() Method 

Overview

The decorator pattern is a structural pattern, that allows you to attach additional responsibilities to an object at runtime.

The decorator pattern is used in both the Object Oriented and Functional paradigms.

The decorator pattern is different than the Python language feature of Python Decorators in its syntax and complete purpose. It is a similar concept in the way that it is a wrapper, but it also can be applied at runtime dynamically.

The decorator pattern adds extensibility without modifying the original object.

The decorator forwards requests to the enclosed object and can perform extra actions.

You can nest decorators recursively.

Terminology

  • Component Interface: An interface for objects.
  • Component: The object that may be decorated.
  • Decorator: The class that applies the extra responsibilities to the component being decorated. It also implements the same component interface.

Decorator UML Diagram

Decorator Pattern UML Diagram

Source Code

./decorator/decorator_concept.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# pylint: disable=too-few-public-methods
"Decorator Concept Sample Code"
from abc import ABCMeta, abstractmethod

class IComponent(metaclass=ABCMeta):
    "Methods the component must implement"
    @staticmethod
    @abstractmethod
    def method():
        "A method to implement"

class Component(IComponent):
    "A component that can be decorated or not"

    def method(self):
        "An example method"
        return "Component Method"

class Decorator(IComponent):
    "The Decorator also implements the IComponent"

    def __init__(self, obj):
        "Set a reference to the decorated object"
        self.object = obj

    def method(self):
        "A method to implement"
        return f"Decorator Method({self.object.method()})"

# The Client
COMPONENT = Component()
print(COMPONENT.method())
print(Decorator(COMPONENT).method())

Output

1
2
3
python ./decorator/decorator_concept.py
Component Method
Decorator Method(Component Method)

Example Use Case

...Refer to Book or Videos for extra content.

Example UML Diagram

Decorator Pattern in Context

Source Code

./decorator/client.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
"Decorator Use Case Example Code"
from value import Value
from add import Add
from sub import Sub

A = Value(1)
B = Value(2)
C = Value(5)

print(Add(A, B))
print(Add(A, 100))
print(Sub(C, A))
print(Sub(Add(C, B), A))
print(Sub(100, 101))
print(Add(Sub(Add(C, B), A), 100))
print(Sub(123, Add(C, C)))
print(Add(Sub(Add(C, 10), A), 100))
print(A)
print(B)
print(C)

./decorator/interface_value.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# pylint: disable=too-few-public-methods
"The Interface that Value should implement"
from abc import ABCMeta, abstractmethod

class IValue(metaclass=ABCMeta):
    "Methods the component must implement"
    @staticmethod
    @abstractmethod
    def __str__():
        "Override the object to return the value attribute by default"

./decorator/value.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# pylint: disable=too-few-public-methods
"The Custom Value class"
from interface_value import IValue

class Value(IValue):
    "A component that can be decorated or not"

    def __init__(self, value):
        self.value = value

    def __str__(self):
        return str(self.value)

./decorator/add.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# pylint: disable=too-few-public-methods
"The Add Decorator"
from interface_value import IValue

class Add(IValue):
    "A Decorator that Adds a number to a number"

    def __init__(self, val1, val2):
        # val1 and val2 can be int or the custom Value
        # object that contains the `value` attribute
        val1 = getattr(val1, 'value', val1)
        val2 = getattr(val2, 'value', val2)
        self.value = val1 + val2

    def __str__(self):
        return str(self.value)

./decorator/sub.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# pylint: disable=too-few-public-methods
"The Subtract Decorator"
from interface_value import IValue

class Sub(IValue):
    "A Decorator that subtracts a number from a number"

    def __init__(self, val1, val2):
        # val1 and val2 can be int or the custom Value
        # object that contains the `value` attribute
        val1 = getattr(val1, 'value', val1)
        val2 = getattr(val2, 'value', val2)
        self.value = val1 - val2

    def __str__(self):
        return str(self.value)

Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
python ./decorator/client.py
3
101
4
6
-1
106
113
114
1
2
5

New Coding Concepts

Dunder __str__ method

When you print() an object, it will print out the objects type and memory location in hex.

1
2
3
4
class ExampleClass:
    abc = 123

print(ExampleClass())

Outputs

1
<__main__.ExampleClass object at 0x00000283038B1D00>

You can change this default output by implementing the __str__ dunder method in your class. Dunder is short for saying double underscore.

Dunder methods are predefined methods in python that you can override with your own implementations.

1
2
3
4
5
6
7
class ExampleClass:
    abc = 123

    def __str__(self):
        return "Something different"

print(ExampleClass())

Now outputs

1
Something different

In all the classes in the above use case example that implement the IValue interface, the __str__ method is overridden to return a string version of the integer value. This allows to print the numerical value of any object that implements the IValue interface rather than printing a string that resembles something like below.

1
<__main__.ValueClass object at 0x00000283038B1D00>

The __str__ dunder was also overridden in the Protoype concept code.

Python getattr() Function

Syntax: getattr(object, attribute, default)

In the Sub and Add classes, I use the getattr() method like a ternary operator.

When initializing the Add or Sub classes, you have the option of providing an integer or an existing instance of the Value, Sub or Add classes.

So, for example, the line in the Sub class,

1
val1 = getattr(val1, 'value', val1)

is saying, if the val1 just passed into the function already has an attribute value, then val1 must be an object of Value, Sub or Add . Otherwise, the val1 that was passed in is a new integer and it will use that instead to calculate the final value of the instance on the next few lines of code. This behavior allows the Sub and Add classes to be used recursively.

E.g.,

1
2
A = Value(2)
Add(Sub(Add(200, 15), A), 100)

Summary

...Refer to Book or Videos for extra content.