mirror of
https://github.com/rjNemo/design-patterns
synced 2026-06-06 02:26:40 +00:00
Merge branch 'master' into facade
This commit is contained in:
commit
6976f4ad0a
11 changed files with 150 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,4 +2,5 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
.vscode
|
.vscode
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
|
.idea/
|
||||||
|
|
@ -14,4 +14,5 @@
|
||||||
- [Adapter](structural/adapter/README.md)
|
- [Adapter](structural/adapter/README.md)
|
||||||
- [Bridge](structural/bridge/README.md)
|
- [Bridge](structural/bridge/README.md)
|
||||||
- [Composite](structural/composite/README.md)
|
- [Composite](structural/composite/README.md)
|
||||||
|
- [Decorator](structural/decorator/README.md)
|
||||||
- [Facade](structural/facade/README.md)
|
- [Facade](structural/facade/README.md)
|
||||||
|
|
|
||||||
0
__init__.py
Normal file
0
__init__.py
Normal file
|
|
@ -5,4 +5,5 @@ Structural patterns explain how to assemble objects and classes into larger stru
|
||||||
- [Adapter](adapter/README.md)
|
- [Adapter](adapter/README.md)
|
||||||
- [Bridge](bridge/README.md)
|
- [Bridge](bridge/README.md)
|
||||||
- [Composite](composite/README.md)
|
- [Composite](composite/README.md)
|
||||||
|
- [Decorator](decorator/README.md)
|
||||||
- [Facade](facade/README.md)
|
- [Facade](facade/README.md)
|
||||||
|
|
|
||||||
49
structural/decorator/README.md
Normal file
49
structural/decorator/README.md
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Decorator
|
||||||
|
|
||||||
|
|
||||||
|
Decorator is a Conceptual pattern that allows adding new behaviors to objects dynamically by placing them inside special wrapper objects.
|
||||||
|
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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. 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. 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.
|
||||||
|
|
||||||
|
At some point, you realize that users of the library expect more than just email notifications. 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. Now the client was supposed to instantiate the desired notification class and use it for all further notifications.
|
||||||
|
|
||||||
|
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. However, it quickly became apparent that this approach would bloat the code immensely, not only the library code but the client code as well.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
“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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## How to Implement
|
||||||
|
|
||||||
|
1. Make sure your business domain can be represented as a primary component with multiple optional layers over it.
|
||||||
|
|
||||||
|
1. Figure out what methods are common to both the primary component and the optional layers. Create a component interface and declare those methods there.
|
||||||
|
|
||||||
|
1. Create a concrete component class and define the base behavior in it.
|
||||||
|
|
||||||
|
1. Create a base decorator class. It should have a field for storing a reference to a wrapped object. The field should be declared with the component interface type to allow linking to concrete components as well as decorators. The base decorator must delegate all work to the wrapped object.
|
||||||
|
|
||||||
|
1. Make sure all classes implement the component interface.
|
||||||
|
|
||||||
|
1. Create concrete decorators by extending them from the base decorator. A concrete decorator must execute its behavior before or after the call to the parent method (which always delegates to the wrapped object).
|
||||||
|
|
||||||
|
1. The client code must be responsible for creating decorators and composing them in the way the client needs.
|
||||||
0
structural/decorator/__init__.py
Normal file
0
structural/decorator/__init__.py
Normal file
8
structural/decorator/component.py
Normal file
8
structural/decorator/component.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
class Component:
|
||||||
|
"""
|
||||||
|
The base Component interface defines operations that can be altered by
|
||||||
|
decorators.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def operation(self) -> str:
|
||||||
|
pass
|
||||||
10
structural/decorator/concrete_component.py
Normal file
10
structural/decorator/concrete_component.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
from component import Component
|
||||||
|
|
||||||
|
class ConcreteComponent(Component):
|
||||||
|
"""
|
||||||
|
Concrete Components provide default implementations of the operations. There
|
||||||
|
might be several variations of these classes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def operation(self) -> str:
|
||||||
|
return "ConcreteComponent"
|
||||||
26
structural/decorator/concrete_decorators.py
Normal file
26
structural/decorator/concrete_decorators.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
from decorator import Decorator
|
||||||
|
|
||||||
|
|
||||||
|
class ConcreteDecoratorA(Decorator):
|
||||||
|
"""
|
||||||
|
Concrete Decorators call the wrapped object and alter its result in some
|
||||||
|
way.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def operation(self) -> str:
|
||||||
|
"""
|
||||||
|
Decorators may call parent implementation of the operation, instead of
|
||||||
|
calling the wrapped object directly. This approach simplifies extension
|
||||||
|
of decorator classes.
|
||||||
|
"""
|
||||||
|
return f"ConcreteDecoratorA({self.component.operation()})"
|
||||||
|
|
||||||
|
|
||||||
|
class ConcreteDecoratorB(Decorator):
|
||||||
|
"""
|
||||||
|
Decorators can execute their behavior either before or after the call to a
|
||||||
|
wrapped object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def operation(self) -> str:
|
||||||
|
return f"ConcreteDecoratorB({self.component.operation()})"
|
||||||
23
structural/decorator/decorator.py
Normal file
23
structural/decorator/decorator.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
from component import Component
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Decorator(Component):
|
||||||
|
"""
|
||||||
|
The base Decorator class follows the same interface as the other components.
|
||||||
|
The primary purpose of this class is to define the wrapping interface for
|
||||||
|
all concrete decorators. The default implementation of the wrapping code
|
||||||
|
might include a field for storing a wrapped component and the means to
|
||||||
|
initialize it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_component: Component
|
||||||
|
|
||||||
|
@property
|
||||||
|
def component(self) -> Component:
|
||||||
|
"""The Decorator delegates all work to the wrapped component."""
|
||||||
|
return self._component
|
||||||
|
|
||||||
|
def operation(self) -> str:
|
||||||
|
return self._component.operation()
|
||||||
30
structural/decorator/main.py
Normal file
30
structural/decorator/main.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from component import Component
|
||||||
|
from concrete_component import ConcreteComponent
|
||||||
|
from concrete_decorators import ConcreteDecoratorA, ConcreteDecoratorB
|
||||||
|
|
||||||
|
|
||||||
|
def client_code(component: Component):
|
||||||
|
"""
|
||||||
|
The client code works with all objects using the Component interface. This
|
||||||
|
way it can stay independent of the concrete classes of components it works
|
||||||
|
with.
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(f"RESULT: {component.operation()}", end='')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# This way the client code can support both simple components...
|
||||||
|
simple = ConcreteComponent()
|
||||||
|
print("Client: I've got a simple component:")
|
||||||
|
client_code(simple)
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
# ...as well as decorated ones.
|
||||||
|
|
||||||
|
# Note how decorators can wrap not only simple components but the other
|
||||||
|
# decorators as well.
|
||||||
|
decorator1 = ConcreteDecoratorA(simple)
|
||||||
|
decorator2 = ConcreteDecoratorB(decorator1)
|
||||||
|
print("Client: Now I've got a decorated component:")
|
||||||
|
client_code(decorator2)
|
||||||
Loading…
Reference in a new issue