Contents
Decorator
Fron GoF Attach additional responsibilities to an object
dynamically. Decorators provide a flexible alternative to subclassing for
extending functionality.
Despite what the GoF book says, subclassing is a part of
Decorator but is very much reduced. As an example in the diagram above,
'Component' could be Control, 'ConcreteComponent' could be TextBox, Decorator
would be TextBoxDecorator, and the 'ContreteDecoratorX' classes might be
BorderedTextBox, NumericTextBox, DateTextBox, FlashingTextBox, etc. The point
of Decorator is that it allows you to combine the Concrete decorations without
further subclassing: by combining the decorations you can get a bordered,
flashing, numeric TextBox. Other possibilities are decorating a custom control
to include a ListBox, a variety of CheckBoxes etc.
In the examples I have given above, there is no reason why
the TextBoxDecorator should not inherit from TextBox, or even have the concrete
decorators inherit from TextBox and do away with the TextBoxDecorator
completely. A more general implementation would have decorator classes
ControlDecorator, BorderedControlDecorator, etc. (The NumericTextBox and
DateTextBox would not make sense in this scenario, as any control could be
decorated by these classes, and it's only some controls where forcing input to
be numeric or a date makes sense.)
As to the mechanics of the code, the ConcreteDecorator would
take the object it is decorating as a parameter to its constructor. Note that
it will have a common ancestor to this object, and so can treat this object in
great part as the same type � almost � as itself. As an example, start with a
class called Circle with the following interface (implementation is excluded
for brevity):
public class Circle {
public virtual Point Center {
get;
set;
}
public virtual int Radius {
get;
set;
}
public virtual void Draw(Graphics g, Pen p){
// draw the circle
}
}
We can now define Decorators like:
public class BoxCircle : Circle{
private Circle circle;
public BoxCircle(Circle circle) {
this.circle = circle;
}
public override Point Center {
get { return circle.Center; }
set { circle.Center = value; }
}
public override int Radius {
get { return circle.Radius; }
set { circle.Radius = value; }
}
public override void Draw(Graphics g, Pen p){
circle.Draw(g, p);
Rectangle rect = new Rectangle(
new Point(circle.Center.X - circle.Radius,
circle.Center.Y - circle.Radius),
new Size(circle.Radius * 2, circle.Radius * 2)
);
g.DrawRectangle(p, rect);
}
}
Usage would be as follows:
Circle plain = new Circle();
plain.Center = new Point(200, 200);
plain.Radius = 20;
BoxCircle boxed = new BoxCircle(plain);
boxed.Draw(e.Graphics, pen);
We can also define say CrossCircle which will draw a cross
in the middle of the circle. Usage would be exactly as above, but we can also
combine the two concrete decorators to get a circle with a box drawn around it,
and a cross drawn in the middle:
Circle plain = new Circle();
plain.Center = new Point(200, 200);
plain.Radius = 20;
CrossCircle crossedCircle = new CrossCircle(plain);
BoxCircle boxNcross = new BoxCircle(crossedCircle);
boxNcross.Draw(e.Graphics, pen);
This is the main point of this Pattern. It prevents an
explosion of subclasses. We have one concrete class (Circle) and two
decorators. This gives us a total of 4 possible styles; three decorators would
give 8 styles; four decorators would give 15 styles; and so on. This is
potentially a huge saving in complexity and maintenance costs.
As an extension of the example above, note that the boxed
circle uses the base circle's size and draws its box to that size. If it
enlarged itself and drew its box just outside the circle then it could be
applied twice: this would draw a circle within a box within a box. Thus the
same decorator can be applied multiple times.
In the canonical example � with an abstract Decorator
inheriting from a superclass of the object to be decorated � the decorator is
not coupled to the decoratee. In the circle example I have given, the
decorators are coupled to the decoratee, which is not necessarily ideal; the
boxing decorator could be used far more generally (it would have to use a
different method to find the extent of the box to draw, but that's trivial),
however drawing a cross right through the middle of many controls is probably
not a good idea so it may be sensible to couple this decorator to something
specific.
Documented used of Decorator I can find are nearly always
visual (that is, in GUI Frameworks � as is my example). However, here are some
that are not:
- Smart
Pointers in C++ are decorators which count references.
- The
.NET Framework has a number of classes which have a Synchronized method.
This method returns a thread-safe version of the object. This appears to
be done by decorators (it is in Java).
- A
Collection class can be decorated to sort the contents in various ways.
For instance, a Hashtable has no direct way to either use foreach to
iterate over its contents, nor to sort them. Decorators can be applied to
do either of these and applied in series to do both.
- Streams
can be decorated to provide various pre- and post-processing operations
such as compressing, converting to/from ASCII and Unicode, and so on.
Decorator should be used where an
object has a number of optional extras, and these extras may be added ad-hoc in
any combination.
Other points from http://www.stat.cmu.edu/~minka/patterns/Decorator.html:
- A subject and its decorators are
decoupled. The author of the subject does not need to do anything special
for it to be decorated. Similarly, decoratees do not need to prepare for
being decorated.
- It is easy to add any combination of
capabilities. The same capability can even be added twice. This is
difficult with inheritance.
- The same object may be simultaneously
decorated in different ways. Clients can choose what capabilities they
want by sending messages to the appropriate decorator.
- Objects do not pay for capabilities they
do not use. Thus we have efficiency and generality at the same time.
- While
a decorator has the same interface as its subject, it is not the same
object. Hence object identity is not compatible with decorators. This also
makes it hard to add a new decorator at run-time, since all client
pointers must be changed. [svs: I am not sure of the validity of the 'all client pointers must be
changed' comment; it may be language specific. The point that object
identity is changed is a valid one and should be bourn in mind.]
- Delegation may be required for self calls
to work properly.
- If the subject class is heavyweight, with lots of data or methods,
it may make decorators too costly. Instead of changing the skin of the
object, you can change the guts, via the Strategy
pattern. Strategies do not have to conform to the subject's interface.
The Strategy pattern can always replace the Decorator pattern, but it
requires more anticipation. The Decorator pattern requires virtually no
anticipation.
- Sometimes objects need to
call themselves or pass themselves to other objects. What should the
subject do in this case? Should it pass itself or the decorators? If it
should pass the decorators, then it needs to have some way of knowing
about them. (Note that the Strategy
pattern doesn't have this difficulty.) One way is delegation, where
the decorator passes a reference to itself when it forwards the request.
That way the subject knows who was the original recipient - but then
the subject must know it is going to be decorated.
Comments drawn from GoF.
- Adaptor provides a different interface
to its subject. Proxy provides the same interface. Decorator provides an
enhanced interface. [GoF, p216]
- Adaptor changes an object's interface;
Decorator enhances an object's responsibilities. Decorator is thus more
transparent to the client. As a consequence, Decorator supports recursive
composition, which isn't possible with pure Adapters. [GoF, p149]
- Composite and Decorator have similar
structure diagrams, reflecting the fact that both rely on recursive
composition to organize an open-ended number of objects. [GoF, p219]
- 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.
[GoF, p184]
- Decorator is designed to let you add
responsibilities to objects without subclassing. Composite's focus is not
on embellishment but on representation. These intents are distinct but
complementary. Consequently, Composite and Decorator are often used in
concert. [GoF, p220]
- Composite
could use Chain of Responsibility
to let components access global properties through their
parent. It could also use Decorator to override these properties on parts
of the composition. [GoF, p349]
- Decorator and Proxy have different
purposes but similar structures. Both describe how to provide a level of
indirection to another object, and the implementations keep a reference to
the object to which they forward requests. [GoF, p220]
- Decorator lets you change the skin of
an object. Strategy lets you change the guts. [GoF, p184]