Applied Design Patterns with Java
Structural :: Decorator (175) {C ch 12}
Implementation
Here are implementation issues to consider when using the
Decorator pattern:
- Interface conformance. A Decorator object's interface must conform to the interface of the component it decorates. ConcreteDecorator classes must therefore
inherit from a common class.
- Omitting the abstract Decorator class. There's no need to define an abstract Decorator class when only
one responsibility needs to be added. With an existing class hierarchy, rather than a new one, merge Decorator's
responsibility for forwarding requests to the component into the ConcreteDecorator.
- Keeping Component classes lightweight. To ensure a conforming interface, components and decorators must descend
from a common Component class. It's important to keep this common class lightweight; it should focus on defining
an interface, not on storing data. The definition of the data representation should be deferred to subclasses;
otherwise the complexity of the Component class might make the decorators too heavyweight to use in quantity. Putting
more functionality into Component also increases the probability that concrete subclasses will pay for features
they don't need.
- Changing the skin of an object versus changing its guts.
A Decorator is a skin over an object that changes its behavior. An alternative
is to change the object's guts. The Strategy (315) pattern is a good example of a pattern for changing the guts.
Strategies are a better choice in situations where the Component class is intrinsically heavyweight, thereby making
the Decorator pattern too costly to apply. Support different border styles by having the component defer
border-drawing to a separate Border object. The Border object is a Strategy object that encapsulates a border-drawing strategy. By extending
the number of strategies from just one to an open-ended list, the same effect as nesting decorators recursively
is achieved. Graphical components (called "views") maintain a list of "adorner" objects that
can attach additional adornments like borders to a view component. If a view has any adorners attached, it gives
them a chance to draw additional embellishments. The View class is heavyweight. It would be too expensive to use
a full-fledged View just to add a border. Since the Decorator pattern only changes a component from the outside, the component
doesn't have to know anything about its decorators; that is, the decorators are transparent to the component: With
strategies, the component itself knows about possible extensions. So it has to reference and maintain the corresponding
strategies:
- The Strategy-based
approach might require modifying the component to accommodate new extensions.
On the other hand, a Strategy can have its own specialized interface, whereas a Decorator's interface must conform to
the component's. A Strategy for rendering a border need only define the interface for rendering a border (DrawBorder, GetWidth, etc.), so that the Strategy can
be lightweight even if the Component class is heavyweight. A view maintains a list of "behavior" objects
that can modify and intercept events. The view gives each of the registered behavior objects a chance to handle
the event before nonregistered behaviors, overriding them. Decorate a view with special keyboard-handling support
by registering a behavior object that intercepts and handles key events
Related Patterns
Adapter (139): A Decorator is different from an Adapter in that a Decorator only changes an object's responsibilities, not its interface;
an Adapter will give an object a completely new interface.
Composite (163): A Decorator can be viewed as a degenerate composite with only one component.
However, a Decorator adds additional responsibilities—it isn't intended for object aggregation.
Strategy (315): A Decorator allows changing the skin of an object; a Strategy
allows changing the guts. These are two alternative
ways of changing an object.
Catalog Structural Prev Next