Decorator Pattern
Warning
Decorator is pattern is different than python decorators such as
@classmethod
,@staticmethod
or any custom python decorator etc.
In Decorator pattern, an class implements the same interface of the instance it wraps, and delegates the call to actual method call with some extra work done.
In theory, if an instance is wrapped by another class using decorator pattern, can be passed to anywhere where the actual instance was meant to be used.
Decorator pattern in static languages
To implement decorator pattern in static language such as c++
and java
, we need to implement all methods, getters and setters. This is a tedious task.
Decorator in dynamic languages
To implement decorator pattern in dynamic languages such python specifically, we can only implement methods which are required.
class FileWithLogger:
def __init__(self, fs, logger):
self.fs = fs
self.logger = logger
def write(self, text):
self.fs.write(text)
self.logger.log(text)
However, there might be issues if at some area, some other method is required which is not implemented by decorator wrapper. For example, FileWithLogger
cannot be used as context manager using with
because it does not implement methods __enter__
and __exit__
required for with
.
This problem can be solved by using dynamic wrapper where wrapper implements __getattr__
and __setattr__
.
class FileWithLogger:
def __init__(self, fs, logger):
self.fs = fs
self.logger = logger
def write(self, text):
self.fs.write(text)
self.logger.log(text)
def __iter__(self):
return self.__dict__['fs'].__iter__()
def __next__(self):
return self.__dict__['fs'].__next__()
def __getattr__(self, name):
return getattr(self.__dict__['fs'], name)
def __setattr__(self, name, value):
if name in ('fs', 'logger'):
self.__dict__[name] = value
else:
setattr(self.__dict__['fs'], name, value)
def __delattr__(self, name):
delattr(self.__dict__['fs'], name)
However, dynamic wrapper like this will not work for introspection where code analyses the code. Code introspecting the wrapper will find out that this not the actual instance, instead a wrapper.
So does the python patterns guide maxim say,
“The Decorator Pattern in Python supports programming — but not metaprogramming.”
References
https://python-patterns.guide/gang-of-four/decorator-pattern/