This page looks best with JavaScript enabled

Design Patterns - Decorator

 ·  ☕ 3 min read  ·  🤖 Kaung

Context

My path to software engineering career was not traditional. I started out at a coding bootcamp and learned Java and OOP concepts in a short amount of time. I was aware of common design patterns such as factory, singleton, etc. However, I never got to apply them at my job, which was intense on finance, analytics, system engineering, and data munging.

Decorator pattern was the very first one that I got to implement, and I remember it clearly because without it, I would have written multiple redundant lines of code across multiple complex scripts and methods. It saved me months of work and provided me with a clever and decent solution.

So, here I go with an example of decorator pattern.

Decorator Pattern

Decorator pattern is a design pattern which allows a new behavior to be added to an object. The trick is that it allows us to do so without changing the underlying behavior, plus it can be ad-hoc, i.e. added or removed dynamically.

Let’s take a practical experience of mine. We have a set of methods that perform simple math calculation or transform data. The program is already written and in production. However, the critical component has been left out - exception handling. The methods are called millions of times for incoming data streams, and the program must complete. Junk data can come in and cause errors, but we must ignore and continue processing, so majority of data is still available.

The methods are spread across multiple scripts, used everywhere, and there is no way to track them.

We throw in decorator pattern. It takes in a target method and executes it inside exception block and log it accordingly for easy debugging. We can even specify which exceptions to watch out for.

Here is a code example.

1
2
3
4
class DataProcessor:
    
    def calculateMax(self, num1, num2):
        return max(num1, num2)

In above class, we can see that calculateMax takes in numbers - be it in int, float, etc. But the incoming data stream may have a null or empty string or some junk data. This would throw an error and stop the program from progressing. Below is the sample code which catches the error.

Note that this example may be specific to Python, because strong-typed languages will definitely check the object type by default. Consider this “just” as an example for decorator pattern.

 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
class Decorator:
    def catchNonIntegerException(self, func):
        def wrapper(*args, **kwargs):
            try:
                result = func(*args, **kwargs)
            except Exception as e:
                print(e)
                return 'Error'
            return result
        return wrapper

class DataProcessor:

    # Initialize decorator instance here
    decorator = Decorator()

    # Declare the specific decorator for the method
    # You can easily switch decorators here
    @decorator.catchNonIntegerException
    def calculateMax(self, num1, num2):
        return max(num1, num2)

if __name__ == '__main__':

    dp = DataProcessor()
    print(dp.calculateMax(1, 2))
    print(dp.calculateMax(None, 1))

Summary

In this article, I explained the concept behind decorator design pattern and its practical usage. It is a powerful design pattern that allows flexibility, clarity and elegance in code. If used in the right place, you can save a lot of time and effort as well as avoid production issues.

References

Share on
Support Me

kaung
WRITTEN BY
Kaung
Software Engineer | Physicist