DECORATOR
===
# Decorator
- is a structural design pattern
- that lets you attach new behaviors to objects
- by placing these objects inside special wrapper objects
- that contain the behaviors.
# Problem
```python
# Imagine that you’re working on
# - a notification library
# - which lets other programs
# - notify their users
# - about important events.
#
# The initial version of the library
# - was based on the Notifier class
# - that had only a few fields
# - a constructor and a single send method
class Notifier(object):
def __init__(self):
pass
def send(self, message):
# a single send method
# The method could
# - accept a message argument
# - from a client
# - and send the message
# - to a list of emails
# - that were passed to the notifier via its constructor
print("App::", message)
```
```python
class Application(object):
# A third-party app
# - which acted
# - as a client was supposed
# - to create and configure the notifier object once,
# - and then use it each time something important happened.
def __init__(self):
self.notifier = Notifier()
def set_notifier(self, notifier: Notifier):
self.notifier = notifier
def do_something(self):
self.notifier.send("Alert!")
```
At some point,
- you realize
- that users of the library
- expect more than just email notifications.
```python
# Many of them
# - would like to receive an SMS about critical issues.
# Others
# - would like to be notified on Facebook and,
# - of course, the corporate users would love to get Slack notifications.
# How hard can that be?
# - You extended the Notifier class
# - and put the additional notification methods into new subclasses.
class SMSNotifier(Notifier):
def send(self, message):
print("sms::", message)
class FacebookNotifier(Notifier):
def send(self, message):
print("Facebook::", message)
class SlackNotifier(Notifier):
def send(self, message):
print("Slack::", message)
```
Now the client
- was supposed to instantiate the desired notification class
- and use it for all further notifications.
```python
# But then someone reasonably asked you,
# - “Why can’t you use several notification types at once?
# - If your house is on fire, you’d probably
# want to be informed through # every channel.”
# You tried
# - to address that problem by creating special subclasses
# - which combined several notification methods within one
# class.
class SMSFaceBookNotifier(Notifier):
def send(self, message):
print("SMS&FaceBook::", message)
class SMSSlackNotifier(Notifier):
def send(self, message):
print("SMS&Slack::", message)
class SMSFaceBookSlackNotifier(Notifier):
def send(self, message):
print("SMS&FaceBock&Slack::", message)
class FacebookSlackNotifier(Notifier):
def send(self, message):
print("FaceBock&Slack::", message)
```
```python
# However, it
# - quickly became apparent that this approach
# - would bloat the code immensely,
# - not only the library code
# - but the client code as well.
#
# You have to find some other way
# - to structure notifications classes
# - so that their number
# - won’t accidentally break some Guinness record.
def main():
message = "message"
is_facebook_notifier = True
is_app_notifier = True
if_is_slack_notifier = True
if is_facebook_notifier:
notifier = FacebookNotifier()
notifier.send(message)
if is_app_notifier:
notifier = Notifier()
notifier.send(message)
if if_is_slack_notifier:
notifier = SlackNotifier()
notifier.send(message)
if __name__ == "__main__":
main()
```
# Solution
<!--
Extending a class
- is the first thing
- that comes to mind
- when you need to alter an object’s behavior.
However, inheritance
- has several serious caveats
- that you need to be aware of.
Inheritance is static. You
- can’t alter the behavior
- of an existing object at runtime.
You
- can only replace the whole object
- with another one that’s created
- from a different subclass.
Subclasses can have just one parent class.
- In most languages, inheritance
- doesn’t let a class inherit behaviors
- of multiple classes at the same time.
One of the ways
- to overcome these caveats is
- by using Aggregation or Composition instead of Inheritance.
- Both of the alternatives work almost the same way:
- one object has a reference to another
- and delegates it some work,
- whereas with inheritance,
- the object itself is able to do that work,
- inheriting the behavior from its superclass. -->
<!--“Wrapper” is the alternative
- nickname for the Decorator pattern
- that clearly expresses the main idea of the pattern.
A wrapper
- is an object
- that can be linked
- with some target object.
The wrapper
- contains the same set of methods as the target
- and delegates to it all requests it receives.
However, the wrapper
- may alter the result
- by doing something either before
- or after it passes the request to the target. -->
<!--
When does a simple wrapper
- become the real decorator?
As I mentioned, the wrapper
- implements the same interface as the wrapped object.
- That’s why from the client’s perspective these objects are identical.
Make the wrapper’s
- reference field accept any object
- that follows that interface.
This will let you cover an object in multiple wrappers,
- adding the combined behavior of all the wrappers to it.-->
```python
class Notifier(object):
def send(self, message):
print("App::", message)
class BaseDecorator(object):
# In our notifications example,
# - let’s leave the simple email notification behavior
# - inside the base Notifier class,
# - but turn all other notification methods into decorators.
def __init__(self, notifier):
self.wrappee = notifier
def send(self, message):
self.wrappee.send(message)
```
```python
class SMSDecorator(BaseDecorator):
def send(self, message):
self.wrappee.send(message)
print("sms::", message)
class FacebookDecorator(BaseDecorator):
def send(self, message):
self.wrappee.send(message)
print("Facebook::", message)
class SlackDecorator(BaseDecorator):
def send(self, message):
self.wrappee.send(message)
print("Slack::", message)
```
```python
def main():
message = "message"
is_facebook_notifier = True
is_sms_notifier = True
if_is_slack_notifier = True
notifier = Notifier()
if is_facebook_notifier:
notifier = FacebookDecorator(notifier)
if is_sms_notifier:
notifier = SMSDecorator(notifier)
if if_is_slack_notifier:
notifier = SlackDecorator(notifier)
# Since all decorators implement the same interface
# - as the base notifier,
# - the rest of the client code
# - won’t care whether it
# - works with the “pure” notifier object
# - or the decorated one.
#
# The resulting objects
# - will be structured as a stack.
#
# The last decorator
# - in the stack
# - would be the object
# - that the client actually works with.
#
# We
# - could apply the same approach
# - to other behaviors such as
# - formatting messages
# - or composing the recipient list.
#
# The client
# - can decorate the object
# - with any custom decorators,
# - as long as they
# - follow the same interface as the others.
# notifier.send(message)
notifier.send()
if __name__ == "__main__":
main()
```
# Applicability
Use the Decorator pattern
- when you need to be able
- to assign extra behaviors
- to objects at runtime
- without breaking the code that uses these objects.
<!-- The Decorator
- lets you structure your business logic into layers,
- create a decorator
- for each layer
- and compose objects
- with various combinations
- of this logic at runtime.
The client code
- can treat all these objects in the same way,
- since they all follow a common interface. -->
---
Use the pattern
- when it’s awkward
- or not possible to extend an object’s behavior
- using inheritance.
<!--
Many programming languages
- have the final keyword
- that can be used
- to prevent further extension
- of a class.
For a final class,
- the only way
- to reuse the existing behavior
- would be to wrap the class with your own wrapper,
- using the Decorator pattern.-->