7.3. Decorator¶
EN: Decorator
PL: Dekorator
Type: object
The Decorator design pattern is a structural design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.
Here's a simple example of the Decorator pattern in Python:
>>> class Component:
... def operation(self) -> str:
... return "Component"
...
>>> class Decorator(Component):
... _component: Component = None
...
... def __init__(self, component: Component) -> None:
... self._component = component
...
... def operation(self) -> str:
... return self._component.operation()
...
>>> class ConcreteDecoratorA(Decorator):
... def operation(self) -> str:
... return f"ConcreteDecoratorA({self._component.operation()})"
...
>>> class ConcreteDecoratorB(Decorator):
... def operation(self) -> str:
... return f"ConcreteDecoratorB({self._component.operation()})"
...
>>> simple = Component()
>>> decorator1 = ConcreteDecoratorA(simple)
>>> decorator2 = ConcreteDecoratorB(decorator1)
>>> print(decorator2.operation())
ConcreteDecoratorB(ConcreteDecoratorA(Component))
In this example, Component is an interface for all components, Decorator is a base class for all decorators, and ConcreteDecoratorA and ConcreteDecoratorB are concrete decorators that add behavior to the Component. The Decorator class maintains a reference to a Component object and defines an interface that conforms to Component's interface. The operation method in the Decorator class calls the same method in the Component and then may (or may not) do some extra work before or after calling the Component's method. The ConcreteDecorator classes add new behavior before or after calling the parent method.
7.3.1. Pattern¶
Add additional behavior to an object
7.3.2. Problem¶
What if we want compression and encryption?
What if something else will be added?
from hashlib import sha256
class CloudStream:
def write(self, data: str) -> None:
print(f'Storing: "{data}"')
class EncryptedCloudStream(CloudStream):
def write(self, data: str) -> None:
encrypted: str = self._encrypt(data)
super().write(encrypted)
def _encrypt(self, data: str) -> str:
return sha256(data.encode()).hexdigest()
class CompressedCloudStream(CloudStream):
def write(self, data: str) -> None:
compressed = self._compress(data)
super().write(compressed)
def _compress(self, data: str) -> str:
return data[0:9]
if __name__ == '__main__':
credit_card = '1234-1234-1234-1234'
cloud = CloudStream()
cloud.write(credit_card)
# Storing: "1234-1234-1234-1234"
cloud = EncryptedCloudStream()
cloud.write(credit_card)
# Storing: "3eada3ce701aea4c21f117e1e95b32b2acd0a01dd03e7e57b02d141f5f9c7808"
cloud = CompressedCloudStream()
cloud.write(credit_card)
# Storing: "1234-1234"
7.3.3. Solution¶
from abc import ABC, abstractmethod
from dataclasses import dataclass
from hashlib import sha256
class Stream(ABC):
@abstractmethod
def write(self, data: str) -> None:
pass
class CloudStream(Stream):
def write(self, data: str) -> None:
print(f'Storing: "{data}"')
@dataclass
class EncryptedCloudStream(Stream):
stream: Stream
def write(self, data: str) -> None:
encrypted: str = self._encrypt(data)
self.stream.write(encrypted)
def _encrypt(self, data: str) -> str:
return sha256(data.encode()).hexdigest()
@dataclass
class CompressedCloudStream(Stream):
stream: Stream
def write(self, data: str) -> None:
compressed: str = self._compress(data)
self.stream.write(compressed)
def _compress(self, data: str) -> str:
return data[0:9]
if __name__ == '__main__':
credit_card = '1234-1234-1234-1234'
cloud = CloudStream()
cloud.write(credit_card)
# Storing: "1234-1234-1234-1234"
cloud = EncryptedCloudStream(CloudStream())
cloud.write(credit_card)
# Storing: "3eada3ce701aea4c21f117e1e95b32b2acd0a01dd03e7e57b02d141f5f9c7808"
cloud = EncryptedCloudStream(CompressedCloudStream(CloudStream()))
cloud.write(credit_card)
# Storing: "3eada3ce7"