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

../../_images/designpatterns-decorator-pattern.png

7.3.2. Problem

  • What if we want compression and encryption?

  • What if something else will be added?

design-patterns/structural/img/designpatterns-decorator-problem.png

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

../../_images/designpatterns-decorator-solution.png

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"

7.3.4. Assignments