Decorator Pattern

실리콘·2023년 2월 13일
0

Yes, this is the decorators we use.

@some_decorator
def some_function():
	pass

Intent

Dynamic attachment of responsibilities to an object. A flexible alternative to subclass for extending functionality.

Also Known As

Wrapper

Motivation

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.

Applicability

Use Decorator

  • to add responsibilities to individual objects dynamically and transparently. i.e. w/o affecting other objects
  • for responsibilities that can be withdrawn.
  • when extension by subclassing is impractical. Sometimes a large number of independent extensions are possible and would produce explosion of subclasses to support every combination. Or a class definition may be hidden or otherwise unavailable for subclassing.

Structure

diagram from book

Participants

Component

  • Defines the interfact for objects that can have responsibilities added to them dynamically.

ConcreteComponent

  • Defines and object, where additional responsibilities can be attached.

Decorator

  • maintains a reference to Component object
  • defines an interface that conforms to Component's interface

ConcereteDecorator

  • adds responsibilities to the component.

Collaborations

Decorator forwards requests to its Component object. It may optionally perform additional operations before/after forwarding the request

Consequences

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.

Implementation

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.

Sample code

In book, C++. I also rewrite it in python.

Known uses

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.

profile
software engineer

0개의 댓글