Specialization allows us to add features to a class, but in some situations
we need to add different combinations of features to individual objects. For
example, a grid encapsulates and manages a dynamic two dimensional array of
characters that provides "poor man's" graphics:
Suppose we will also need a shaded grid (i.e., a grid with a shaded
background), a bordered grid (i.e., a grid with a border around it), a
read-only grid (i.e., a grid with disabled plot functions), a shaded-read-only
grid, a bordered-read-only grid, a shaded-bordered grid, and a
shaded-bordered-read-only grid. If we create a class for each combination of
these features we will end up with a complicated hierarchy of seven classes:
Adding a new feature, such as resizable grid, will add six
new derived classes. In general, if we are contemplating n features,
there will be n! (= n * (n - 1) * ... * 1) combinations of these features. This
can lead to a combinatorial explosion in the number of classes we must
implement and maintain.
We can avoid this maintenance nightmare by using delegation instead of
specialization to add features. For each feature we simply create a handle
object called a decorator that implements the feature, then delegates to
its body. The body may be the original component or another decorator. Thus,
decorators can be chained together to dynamically provide any combination of
features:
The Decorator Pattern formalizes this idea:
Decorator [Go4]
Other Names
Wrapper
Problem
In some situations
we may want to add features to an individual object rather than an entire
class.
Solution
For each additional
feature create a new "decorator" class that implements the feature.
The decorator class also implements the interface of the original object by
delegating to the original object or perhaps to an instance of another
decorator class. In this way decorators (i.e. instances of decorator classes)
can be chained together.
Assume we want to add features to an instance of the Component class that
implements a component interface. Each decorator class must also implement the
component interface. In this way the client never knows if it is dealing with a
component or a decorator. Each decorator must be able to delegate to its body,
which may be the original component or another decorator. (Thus, decorators are
component interface clients, too!) For convenience, we place the delegation
machinery in a decorator base class:
For example, the services are virtual functions in the component interface:
interface IComponent {
void serviceA();
void serviceB();
// etc.
}
The basic services are implemented in the Component class:
class Component implements IComponent
{
void serviceA() { ... }
void serviceB() { ... }
// etc.
}
The Decorator base class simply delegates to its body:
class
Decorator implements IComponent {
Decorator(IComponent c) { myBody = c; }
void serviceA() { myBody.serviceA(); }
void serviceB() { myBody.serviceB(); }
// etc.
private IComponent myBody;
}
Suppose the concrete decorator class Decorator1 simply enhances serviceA()
with some added behavior, then it only needs to re-implement serviceA() (the Decorator class provides default
implementations for the other services). The last line performs the delegation:
class Decorator1 extends Decorator {
Decorator1(IComponent c) {
super(c); }
void serviceA() {
// added behavior goes here
super.serviceA();
// delegates to body
}
}
Here is how a client might create a reference, z, to an object that
implements the basic component interface, but with the features added by
Decorator1 and Decorator2:
IComponent x = new Component();
IComponent y = new Decorator1(x);
IComponent z = new Decorator2(y);
Of course the original component and the Decorator1 instance can be
anonymous, so we can create z in a single line:
IComponent z = new Decorator2(new
Decorator1(new Component()));
Here is a sequence diagram showing the chain of delegations that occur when
the client calls z.serviceA():
Be careful, as with the Envelope-Letter idiom, a decorator derives from the
component interface and delegates to an instance of the component
interface. We need decorators to be derived from the component interface so
they can be treated like components by clients and other decorators. However, a
decorator must be careful never to use the functions and attributes it inherits
from the component interface base class. Instead, only the functions and
attributes of the body must be used. For example, assume state is a
component interface attribute.
abstract class IComponent {
protected State state;
// etc.
}
The true state of z is x.state, not the
inherited z.state.
Streams
The decorator pattern is used to add features to java streams. Recall the makeWriter()
method of the Tools class:
static public PrintWriter makeWriter(OutputStream os) {
return new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(os)), true);
}
This method constructs an output stream writer, places a buffered writer
decorator in front of it, then places a print writer
decorator in front of that. Each extends the abstract Writer interface/class:
A call to this method constructs the decorator chain: