(Updated: 2005.04.05 10:55:31 PM)
| |
Decorator Pattern is one of the 23
Design Patterns elucidated in the most excellent
Gamma And Helm book, Design Patterns: Elements of Reusable
Object - Oriented Software by Gamma and Helm et al.
ISBN 0201633612
, which is cited below:
Intent: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. (Also known as Wrapper)
Design Pattern Classification: Object Structural
Applicability:
The
Decorator Pattern is useful when:
to add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects.
for responsibilities that can be withdrawn.
when extension by subclassing is impractical or impossible.
Structure:
Component - defines the interface for objects that can have responsibilities added to them dynamically.
ConcreteComponent - defines an object to which additional responsibilities can be attached.
Decorator - maintains a reference to a Component object and defines an interface that conforms to Component's interface.
ConcreteDecorator - adds responsibilities to the component.
Collaborations:
- Decorator forwards requests to its Component object. It may optionally perform additional operations before and after forwarding the request.
Note - There are several other important implementation issues to consider before dishing out decorator objects, be sure and see the Gamma And Helm book or CD ( ISBN 0201633612
) if you're new to Design Patterns.
Decorator and VFP (By Steven Black)
A decorator wrapper (I'll just call it Decorator) is an elegant OOP technique that can be handy in a wide variety of situations. In particular, the Decorator pattern is useful for extending the functionality of a class without polluting (or even needing) the original source.
Subclassing isn't always cool
But before we get to the decorator pattern, lets examine what happens when we allow a class to grow organically with inheritance. To illustrate, consider the following class interface for a frog:
DEFINE CLASS Frog AS CUSTOM
FUNCTION Jump(nValue)
FUNCTION Eat()
ENDDEFINE
Amphibians are easily created with Visual FoxPro. Suppose that we needed to create a new sort of frog, one that can dance. We could subclass Frog as follows:
DEFINE CLASS DancingFrog AS Frog
FUNCTION Dance()
ENDDEFINE
Suppose we later need a singing Frog. Well, within our current hierarchy, we have several obvious options.
- We could simply augment the DancingFrog with a Sing() method, perhaps renaming the class, if that's possible, to EntertainingFrog to better reflect its enhanced functionality.
- We could subclass the DancingFrog, add a Sing() method to the new subclass, and call the new class, say, DancingSingingFrog.
- We could insert a new Frog subclass in the hierarchy, before DancingFrog, endow it with a Sing() method, and call it, say, SingingDancingFrog.
- We could make all Frogs able to sing by augmenting original Frog class.
- What the heck, we could vastly simplify this rapidly exploding hierarchy by eliminating the DancingFrog class and give every Frog the Sing() and Dance() methods in a single and monolithic Frog class.
So, buffeted by the needs of this current implementation, you make your choice and you take your chances. What if you later needed a burping frog, one that belches after meals. Do we subclass again? If so, where? Do we add a Burp() method, or do we simply augment the Eat() method because problem analysis indicates that burping naturally follows from eating. Again, which hierarchical level is best for modification? And later, when you need a frog that can beg, roll-over, shake-a-leg, and stay, a new question eventually arises: "How did this Frog class become such a mess?".
At the outset, a pragmatic reuse artists would be correct in asking when, if ever, will the need for a singing, dancing, and belching frog ever arise again? Why not just augment the frog for this instance, without polluting with trivial nuance our general, simple, and reusable Frog class?
The Decorator alternative
The answer may be to use a decorator. A decorator is, in essence, a class with mostly "pass-through" behavior. It "wraps" a class by reference, forwarding all messages to the reference except for the messages the wrapper is designed to intercept. Setting up a decorator takes a bit of work, ( Take a look at Decorating With THISAccess for a really simple yet comprehensive way of implementing a decorator in VFP6+ without the "bit of work" ), but thereafter it's a snap to use.
Consider the following DecoFrog class:
DEFINE CLASS DecoFrog AS CUSTOM
oRealFrog= .NULL.
FUNCTION INIT( oFrog)
THIS.oRealFrog= oFrog
FUNCTION Jump(n)
THIS.oRealFrog.Jump(n)
FUNCTION Eat()
THIS.oRealFrog.Eat()
ENDDEFINE
Sample 1. Notice the key element of decorators: They are "transparent", passing through all messages except for those that need to be intercepted. Creating an abstract decorator class (like the one above) that passes through everything is the first step. Now to augment the frog for a particular instance, we can use a simple subclass of the decorator without polluting the Frog class.
To the outside world, the DecoFrog class has the same programming interface as a Frog. But it's not a Frog, it's a lens through which we can "see" a Frog. If we need a specialized one-off Frog, like one that sings, we could do this:
DEFINE CLASS DecoSingingFrog AS FrogDecorator
FUNCTION Sing()
? WAIT WINDOW "It's not easy being green..."
ENDDEFINE
Similarly, we can define a dancing frog by simply subclassing the class DecoFrog as follows:
DEFINE CLASS DecoDancingFrog AS DecoFrog
FUNCTION Dance()
DecoFrog::Jump(+1)
DecoFrog::Jump(-2)
DecoFrog::Jump(+1)
ENDDEFINE
Retrofitting a decorator is easy. Where before your code looked like this:
Kermit=CREATE( "Frog")
A singing frog can now be substituted at run-time like this:
Kermit=CREATE("Frog")
Kermit=CREATE("DecoSingingFrog",Kermit)
or more succinctly:
Kermit=CREATE("DecoSingingFrog",CREATE("Frog"))
(Note that nesting CREATEOBJECT statements work just fine in Visual FoxPro).
Additionally, decorators can be chained, with functionality added or modified as needed! This gives you considerable pay-as-you-go flexibility. For example, to build a singing and dancing frog, do as follows:
Kermit=CREATE("Frog")
Kermit=CREATE("DecoSingingFrog",Kermit)
Kermit=CREATE("DecoDancingFrog",Kermit)
or, if you prefer one-line of code:
Kermit=CREATE("DecoDancingFrog", CREATE("DecoSingingFrog",CREATE("Frog")))
Which creates an object relationship illustrated by the following object diagram:
Figure 1. Two decorators chained together, both augmenting the object. Well, Tada! We now have an ordinary Frog named Kermit that appears, in this instance, with the ability to sing and dance, and we didn't need to pollute the Frog class to get it. In fact, we didn't need the source to class Frog.
Decorator Benefits
Here are some benefits that come from using decorators.
- Decorators can be an effective substitute for multiple-inheritance, which isn't supported in Visual FoxPro. In our Kermit the Frog example above, in some languages we could have used multiple inheritance to combine the Frog class and, say, an Elvis class to produce an entertaining frog. What we did instead is wrap an ordinary Frog object in various entertainment objects to extend the frog's capability as needed.
- You incur resource expenses on a pay-as-you-go basis because you can decide at run-time what types of objects to create. Also, you don't need to foresee all the future functionality of a class, deferring to decorators as new needs arise.
- You can extend the functionality of a class knowing only its interface; source is usually not required.
- If you use a decorator to endow your classes with one-off characteristics, you can neatly avoid extending your class hierarchy to support the characteristic.
- In some situations you may be able to use a lightweight decorator -- one that doesn't expose the whole interface of the wrapped class – in order to accomplish a simple task.
Decorator Downsides
As with all things, the "no free lunch" pattern applies: You cannot get the added flexibility afforded by using a decorator without some tradeoffs. Here are some of them:
- A decorated object is not the same thing as the object itself. In our Frog example, if the line THIS.Eat() appears somewhere within the Frog class, that will call the Frog's Eat() method, and not the Eat() method that may be enhanced by a decorator.
- Decorators are best used with classes that use access functions to expose their properties. Querying and setting decorator properties without pass-through access functions does not touch the object being decorated.
- It takes more time to instantiate an object if one also instantiates one or more decorators, and once created, it takes slightly more time for messages to filter through layers of decorators to reach the object itself.
- People who aren't familiar with decorators are likely to find the proliferation of wrapper objects confusing.
- A decorator is, in fact, a second distinct class whose interface must be maintained in concert with the class it decorates. This can be problematic, but is alleviated somewhat by making the decorator a subclass of an abstract class, as in the diagram below. In this way, Decorators automatically get new interfaces, allows you to wire the "pass-through" behavior at a later time. In the class diagrams below, if the abstract class defines the complete interface for the concrete class (as it should), then new Decorator appear automatically.
Figure 2. One of the difficulties of managing a decorator is maintaining its interface in synch with the objects to be decorated. Problems can be greatly alleviated by making the decorator a subclass of an abstract class, one whose sole purpose is (by definition) to define the class interface.
Figure 3. Here is how the Frog class might look, with emphasis on the evolution of its decorators to enhance otherwise normal and unentertaining frogs.
Conclusion
Decorators change the appearance of an object in a way that is fundamentally different from subclassing. Subclassing changes class internals. A decorator changes the class appearance.
Decorators are not solutions for every situation, but occasionally they are just the ticket. The next time you find yourself sub-classing for the purposes of a particular implementation, ask yourself if a decorator wouldn't better suit your situation.
See also: VFP Design Pattern Catalog Decorating With This Access
Category Design Patterns