Notes and exercises for learning design patterns
class LoggingDecorator(Notifier):
def __init__(self, wrapped: Notifier):
self._wrapped = wrapped
def send(self, recipient: str, message: str) -> None:
print(f"SENDING notification to {recipient}")
self._wrapped.send(recipient, message)
print(f"SENT notification to {recipient}")
class UppercaseDecorator(Notifier):
def __init__(self, wrapped: Notifier):
self._wrapped = wrapped
def send(self, recipient: str, message: str) -> None:
self._wrapped.send(recipient, message.upper())
Composition:
notifier = LoggingDecorator(UppercaseDecorator(EmailNotifier()))
notifier.send("bob@example.com", "server is down")
Output:
SENDING notification to bob@example.com
EMAIL to bob@example.com: SERVER IS DOWN
SENT notification to bob@example.com
Every decorator:
Notifier — so the caller never knows it is wrapped.self._wrapped.send(...) — so the real work still happens.LoggingDecorator only logs, UppercaseDecorator only transforms.Notice that UppercaseDecorator wraps the inner object and LoggingDecorator wraps that:
LoggingDecorator
└── UppercaseDecorator
└── EmailNotifier
The call flows inward:
LoggingDecorator.send
prints "SENDING..."
calls UppercaseDecorator.send
uppercases message
calls EmailNotifier.send
prints "EMAIL to..."
prints "SENT..."
This is why the logged message is already uppercase in the output — uppercasing happens before the email is sent, and logging wraps the entire operation.
self._wrapped.sendclass BrokenDecorator(Notifier):
def __init__(self, wrapped: Notifier):
self._wrapped = wrapped
def send(self, recipient: str, message: str) -> None:
print(f"SENDING to {recipient}")
# forgot to call self._wrapped.send !
This decorator silently swallows the notification. The log appears but nothing is actually sent. Always call through to the wrapped object unless you have a deliberate reason not to (like a stub or a null object).
If you have many decorators, you can put the forwarding in a base class so each decorator only overrides what it changes:
class NotifierDecorator(Notifier):
def __init__(self, wrapped: Notifier):
self._wrapped = wrapped
def send(self, recipient: str, message: str) -> None:
self._wrapped.send(recipient, message)
class LoggingDecorator(NotifierDecorator):
def send(self, recipient: str, message: str) -> None:
print(f"SENDING notification to {recipient}")
super().send(recipient, message)
print(f"SENT notification to {recipient}")
This is not required for this exercise, but becomes useful in exercise 2 when you have several decorators.