Yes, this is the decorators we use.
@some_decorator
def some_function():
pass
Dynamic attachment of responsibilities to an object. A flexible alternative to subclass for extending functionality.
Wrapper
For adding responsibilities to individual objects, but not to an entire class. We can do this w/ inheritance. However, if this responsibility(A)
can be decided statically, we can be more flexible and enclose the component
in another object that does A
. We call this enclosing object Decorator
.
Decorator
conforms to the interface of the component
it decorates, so that its prescence is transparent to the component
's clients. This Decorator
forwards requests to the component
's clients and optionally perform additional actions before/after forwarding. Transparency lets you neset decorators recursively, theoretically unlimited amount. Some example involving text editor is in book.
Again, transparenct allows clients to not depend at all on the decoration.
Use Decorator
diagram from book
Component
ConcreteComponent
Decorator
Component
objectComponent
's interfaceConcereteDecorator
Decorator forwards requests to its Component
object. It may optionally perform additional operations before/after forwarding the request
At least 2 key benefits and 2 liabilities:
1. More flexibility then static inheritance. Instead of creating a class for each responsibility, use decorators to add/remove responsibility at runtime simply by attaching and detaching them. Also, by mixing decorators you can also mix and match responsibilities.
2. Avoids feature-laden classes high up in the hiearchy. Decorators add pay-as-you-go approach to adding responsibilities. Instead of trying to support all foreseeable features in a complex, customizable class, you can define a simple class and add functionality incrementally with Decorator objects. A functionality compose of many responsibilities can be made from simple pieces, the decorator. Thus, application need not pay for features it don't use. Even when unforseen extensions arise, they can be added independently from the classes of objects they extend. Extending a complex class may expose details unrelated to the responsibilities you're adding.
3. A decorator and its component aren't identical. A decorator acts as a transparent enclosure. But from object identity point of view, decorated component and component itself is different. Hence you shouldn't rely on object identitiy when you use decorators. (?maybe this means comparing object using memory address?)
4. Lots of little objects. Often result in systems compose of lots of little objects that look alike, and differ only in the way they are interconnnected. Not in their class of in the value of their variables. These systems can be easy to customize for people who already knows them, but can be hard to learn and debug.
Several issues to consider.
1. Interface conformance. A decorator's object interface must conform to the interface of the component it decorates. ConcreteDecorator
classes must there for inherit from a common class (can be language dependent)
2. Omitting the abstract Decorator class. No need to define an abstract Decorator
class when you only need to add one responsibility.
3. Keeping Component classes lightweight". To ensure a conforming interface, components and decorators must inherit from a common Component
class. Import tant to keep this common class light. Focus on interfacd, not data. Don't make subclasses pay for features they don't need.
4. Changing the skin of an object vs changing its guts. If you need to changes guts, Strategy Pattern is a good example. It is better where Component
class is instrinsically heavyweight, so Decorator pattern is too costly to apply. And as Decorator Pattern changes only the skin, the component being decorated need not know about its decorators. Conversely, in Strategy Pattern the component must know about list of strategies it can use.
Also, strategy pattern can have its own modified interface. Where Decorator must conform to unified interface.
In book, C++. I also rewrite it in python.
Graphical, Streams etc.
Adapter: can also add completely new interface, not just responsibilities.
Composite: can be viewed as degenerate composite w/ one component.
Strategy: Strategy changes guts, Decorator changes skin.