Decorator¶
The decorator is a structural pattern whose main task is to add extra functionality to an existing object without modifying the underlying behavior.
Design¶
The decorator adds functionality without using the inheritance mechanism, and enables you to enrich functionality dynamically (while the application is running). It is based on a common interface (or base class) that wraps the decorated object as well as implements it.
By using a common interface, it is possible to use multiple decorators at once, in any order.
Example¶
The following example consists of the following parts:
- common interface
FragStatistics
- base class to be decorated with
FirstPersonShooterFragStatistics
- four decorators:
DeathCountInfoDecorator
,DisplayCountersDecorator
,FragDeathRatioDecorator
,FragInfoDecorator
- usage case
class FragStatistics:
def increment_frag_count(self):
pass
def increment_death_count(self):
pass
def reset(self):
pass
class FirstPersonShooterFragStatistics(FragStatistics):
def __init__(self):
self._frags_count = 0
self._death_count = 0
def increment_frag_count(self):
self._frags_count += 1
return self._frags_count
def increment_death_count(self):
self._death_count += 1
return self._death_count
def reset(self):
self._frags_count = 0
self._death_count = 0
class DeathCountInfoDecorator(FragStatistics):
def __init__(self, frag_statistics):
self._frag_statistics = frag_statistics
def increment_frag_count(self):
return self._frag_statistics.increment_frag_count()
def increment_death_count(self):
print('Fragged by an enemy')
return self._frag_statistics.increment_death_count()
def reset(self):
self._frag_statistics.reset()
class DisplayCountersDecorator(FragStatistics):
def __init__(self, frag_statistics):
self._frag_statistics = frag_statistics
def increment_frag_count(self):
frag_count = self._frag_statistics.increment_frag_count()
print(f"Your frag count is: {frag_count}")
return frag_count
def increment_death_count(self):
death_count = self._frag_statistics.increment_death_count()
print(f"Your death count is: {death_count}")
return death_count
def reset(self):
self._frag_statistics.reset()
print("Stats reset - KDR is equal to 0")
class FragDeathRatioDecorator(FragStatistics):
def __init__(self, frag_statistics):
self._frag_statistics = frag_statistics
self._current_frag_count = None
self._current_death_count = None
def increment_frag_count(self):
self._current_frag_count = self._frag_statistics.increment_frag_count()
self._display_frag_deaths_ratio()
return self._current_frag_count
def increment_death_count(self):
self._current_death_count = self._frag_statistics.increment_death_count()
self._display_frag_deaths_ratio()
return self._current_death_count
def _display_frag_deaths_ratio(self):
if self._current_frag_count and self._current_death_count:
print(f'KDR is {self._current_frag_count/self._current_death_count}')
def reset(self):
self._frag_statistics.reset()
class FragInfoDecorator(FragStatistics):
def __init__(self, frag_statistics):
self._frag_statistics = frag_statistics
def increment_frag_count(self):
print('Enemy fragged')
return self._frag_statistics.increment_frag_count()
def increment_death_count(self):
return self._frag_statistics.increment_death_count()
def reset(self):
self._frag_statistics.reset()
def main():
statistics = FirstPersonShooterFragStatistics()
statistics.increment_death_count() # nothing will appear on the screen
statistics.increment_frag_count() # nothing will appear on the screen
decorated_statistics = FragDeathRatioDecorator(FragInfoDecorator(DisplayCountersDecorator(DeathCountInfoDecorator(statistics))))
decorated_statistics.increment_frag_count()
decorated_statistics.increment_frag_count()
decorated_statistics.increment_frag_count()
decorated_statistics.increment_death_count()
if __name__ == '__main__':
main()
After executing the main
method, the following output will appear on the screen:
Enemy fragged
Your frag count is: 2
Enemy fragged
Your frag count is: 3
Enemy fragged
Your frag count is: 4
Fragged by an enemy
Your death count is: 2
KDR is 2.0
Note that the order of the decorative classes does not matter much to the functionality of the application.
Built-in python decorator¶
In Python, the decorator implementation can be based on the functionality of built-in decorators. The Python decorator technically does not correspond to the constructs described above: it is a callable entity that takes a function object and returns another function object.
The following example shows how to implement a cache that stores the results of previous computations with built-in decorators:
import functools
def cache(fn):
_cache = dict()
@functools.wraps(fn)
def cacher(*args):
if args not in _cache:
_cache[args] = fn(*args)
return _cache[args]
return cacher
@cache
def fibonacci(n):
if n in (0, 1):
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
def main():
fibonacci(35)
if __name__ == '__main__':
main()