![]() |
|
|
Decorator Pattern Samuel Lee, George Luo |
IntroductionAs more programs use the object oriented technology, the design of object oriented software becomes more important than ever. Software design pattern allows developers to reuse successful design and architecture more easily. In this paper, a type of software design pattern called Decorator is introduced.
DefinitionDesign pattern provides a solution to a problem that occurs over and over again within a specific context and by identifying the patterns in the software design, it allows us to reuse the same structure on similar problems and also help software developers to communicate more efficiently through a common knowledge. One type of a structural pattern is the Decorator pattern. By being a structural pattern which means that Decorator is mostly concerned with how objects and classes are composed to form this structure. The main intention for Decorator is to provide a more flexible and dynamic way of adding functionality to an object when comparing to inheritance or subclassing approach.
In the inheritance approach, we extend the responsibility of a particular class by using subclasses. Even though this approach is quick and easy but it does not provide flexibility because there is not an option of extending the functionality only to an object, instead of whole class. A more flexible way of extending a responsibility is to use the Decorator pattern where the component or object is enclosed in another Decorator object and at run time this Decorator object will add extra responsibility to what the enclosed object already provide. One other evidence that the Decorator is more flexible than the inheritance approach is that decorators can be chained together dynamically. Each Decorator provide extra responsibility to an object and by chaining them together (a Decorator enclose another Decorator) it will provide an unlimited number of extra functionalities.
Another way of looking at the Decorator pattern is that Decorator changes the skin of an object without changing the internal guts of an object. For example, the cell phones today allow us to change the faceplate or even the antenna of the cell phone to make it more stylish looking or a flashing light will appear on the antenna when a call is received. The faceplate and antenna are the added feature (additional responsibility in object) of the cell phone where a user can decide to change it when ever they want (at run time), and unlike the chips, speakers or number keys are set statically at the factory. So software decorators are like cell phone plates or antenna, where it encapsulates some functionality that can be snap onto or add on to an existing object.
The Decorator is also known as the Wrapper because it extends the functionality by wrapping itself on top of an object.
Decorator Pattern Structure
Motivation
![]()
Figure 1: Decorator Pattern Structure (Kremer, 2003)
By looking at the structure of the decorator pattern, we can clearly define the pattern into four classes. Here is the explanation for each of the class:
Now we use an example to illustrate the above definition.
- Component: Defines the interface for objects that can have responsibilities added to them dynamically.
- Concrete Component: Defines an object to which additional responsibilities can be attached.
- Decorator: Maintains a reference to Component object and defines an interface which conforms to Component's interface.
- Concrete Decorator: Adds responsibilities to the component
Figure 2: Decorator Pattern Structure Example (Duell, 1999)
On this diagram, the Visual Component corresponds to the Component. The Painting corresponds to the Concrete Component. The frame and matte correspond to the Concrete Decorator.
We know painting (Concrete Component) can be hung on a wall with or without frames (Concrete Decorators). Normally, painting will be framed and matted before hanging. Just Adding or removing frames and mattes to or from an individual painting provide more flexibility than requiring all paintings to have the same frame. The Decorators are separate from Component.
ImplementationIt was mentioned in the paragraphs above, one way to add responsibility is inheritance. But it is inflexible and the additional responsibility is made statically. Sometimes we just need to add responsibilities in an individual objects, not to entire class which inheritance does. A more flexible way is to enclose an object in the component that adds the functionality and the enclosing object is called a decorator.
A Decorator must conform to the interface of the component and because of this regulation; the Decorator is also transparent to the component. When a Decorator queries a component, an additional action may be performed before or after querying. The transparency character of the Decorator pattern lets you nest Decorators recursively. A conclusion to the Decorator pattern is that Decorators can be added in anywhere of a component. In general, clients don’t need to worry about how the decorator adds responsibilities, it is Concrete Decorators’ responsibility to extend and implement all the extra functionalities.
Suppose we have a TextView object (Concrete Component) that displays text in a window, but it has no scroll bars and border on it by default. When we are going to add them in the window, we can apply ScrollDecorator and BorderDecorator (Concrete Decorators) to do these. Suppose we have defined a VisualWindow (Component) as the abstract class for the TextView up front. It defines their drawing and event handling interface. The ScrollDecorator and BorderDecorator are subclasses of decorator. The Decorator class simply forwards drawing requests to VisualWindow. Decorator subclasses: ScrollDecorator and BorderDecorator. They will add scroll bar and border in the VisualWindow. An example of the Decorator being called in a nested way looks like this:
VisaulWindow.addComponent(new BorderDecorator(new ScrollDecorator(new TextView())));
ConsequencesBefore we actually go into detail on some of the pitfalls, hints or techniques that we should be aware of in terms of applying Decorator pattern, let's take a look at the example (Whitney, 2001) below.
If we want to implement text view with the following features:
side scroll bar Bottom scroll bar 3D border Flat border With all the combination of features, it will produce up to 12 different options:
TextView TextViewWithNoBorder&SideScrollbar TextViewWithNoBorder&BottomScrollbar TextViewWithNoBorder&Bottom&SideScrollbar TextViewWith3DBorder TextViewWith3DBorder&SideScrollbar TextViewWith3DBorder&BottomScrollbar TextViewWith3DBorder&Bottom&SideScrollbar TextViewWithFlatBorder TextViewWithFlatBorder&SideScrollbar TextViewWithFlatBorder&BottomScrollbar TextViewWithFlatBorder&Bottom&SideScrollbar One of the solutions is to implement this example with object composition.
Figure 3: Implement TextView using Object Composition (Whitney, 2001)
The java code for this example may be written as:
class TextView {
Border aBorder;
ScrollBar verticalBar;
ScrollBar horizontalBar;
public void draw() {
aBorder.draw();
verticalBar.draw();
horizontalBar.draw();
code to draw self }
etc.
}By using this approach, we can see that TextView have Border and Scrollbar objects encapsulated inside of the class which means TextView knows everything about Border, Scrollbar and all the variations. Therefore, it is harder for us to add extra features by using this approach since for every features that we add, TextView will need to know about it and that requires modifying the TextView class.
Let’s take a look at the other solution by using the Decorator approach.
Figure 5: Decorator Runtime Structure (Whitney, 2001)
In this example (figure 4) we can see that TextView is a subclass of the VisualComponent class and it does not contain any Border or Scrollbar object. From the runtime structure of the Decorator pattern (figure 5), the TextView object (aTextView) is enclosed in the Concrete Decorator (aScrokkDecorator) and then this object is enclosed in another Concrete Decorator (aBorderDecorator). As mentioned before, this chaining of Decorators and enclosing the object inside the Decorators will add extra responsibilities to the TextView object transparently, which means that by adding extra responsibilities will not affect TextView class at all.
Here are some of the things to remember or consider when implementing your code using Decorator pattern:
- Interface conformance - Decorator object must conform to the interfaces of the component it decorates
- Omitting the abstract Decorator class - consider merging the responsibility right into the Concrete Decorator when adding only one responsibility
- Keeping Component class lightweight - base Component class should focus on the interface and not on storing data because this will make the Decorators too heavyweight which means that we are probably paying for the features that we don not need.
- Should we change the skin of an object or should we change its guts
- One of the guideline is that Strategy pattern (changing the guts) might be a better choice if the Component class is intrinsically heavyweight.
Comparison of Decorator vs. Strategy Pattern
Even though both Decorators and Strategies add responsibilities to an object, there are some differences on how extra responsibilities are implemented.
The Decorator pattern only changes the skin of a component, which means that the component doesn't have to know anything about its Decorators. Hence, the Decorators are transparent to the component. As in the Strategy pattern, the component has reference to the corresponding strategies and that is how it knows all the possible extensions. The advantage of the Strategy pattern is that strategy can have a specialized interface, which means that it can have heavyweight component class but the Strategy class can be lightweight. But the short coming of the Strategy pattern is that when ever we need to add new responsibilities we will need to modify the component class itself. The figures below illustrate both patterns in the run time structure.
Figure 6: Decorator Pattern Runtime Structure (Gamma, 1995)
Figure 7: Strategy Pattern Runtime Structure (Gamma, 1995)
Examples
Here is a java code example (Kuhl, 2000) that illustrates the Decorator pattern.
// An abstract VisualComponent class that defines the interface
abstract class VisualComponent( ) {
public void Draw( );
public void Resize( );
...
}// An abstract Decorator class
// Subclass of VisualComponent and it conforms to the VisualComponent interface
abstract class Decorator extends VisualComponent {
private VisualComponent component;
public Decorator (VisualComponent comp) {
this.component = comp;
} // constructor
public Draw( ) {
component.Draw( );
} // Draw
public Resize( ) {
component.resize( );
}// Resize
...
}//A concrete decorator class that is a subclass of the abstract decorator class
//It still needs to conform to the interface but it added extra responsibility
//This class implements the border functionality
class BorderDecorator extends Decorator {
private int width;
public BorderDecorator(VisualComponent comp, int BorderWidth) {
super(comp); // call constructor of parent class
this.width = BorderWidth;
} // Constructor
private void DrawBorder {
... // code to implement Draw Border method
} // DrawBorder
public Draw( ) {
super.Draw( );
DrawBorder(width);
} // Draw
...
}//A concrete decorator class that is a subclass of the abstract decorator class
//It still needs to conform to the interface but it added extra responsibility
//This class implements the scroll bar functionality
class ScrollDecorator extends Decorator {
private int position = 0;
public ScrollDecorator(VisualComponent comp) {
super(comp); // call constructor of parent class
} // Constructor
private void ScrollTo(int newposition) {
... // code to implement ScrollTo method
} // ScrollTo
private void DrawScroll( ) {
… // code to implement DrawScroll method
} // DrawScroll
public Draw( ) {
super.Draw( );
DrawScroll;
} // Draw
...
}//The client – the main program
//A component object is instantiated and enclosed in the decorator
public class Client{
public static void main(String args[]){
VisualComponent bstext = new BorderDecorator(new ScrollDecorator (new TextView()));
bstext.draw();
}
}
Figure 8: Decorator Pattern Example (Kuhl, 2000)
In figure 8 it illustrate how a decorator adds the functionality and forwards the rest of the request to the enclosed component object to be handled.
Know usesHere are some of the benefits and liabilities of applying Decorator pattern:
Advantage
Shortcoming
- Decorator is more flexible than static inheritance and could be an effective substitution for multiple inheritance in some cases
- Decorator is transparent to its component. While adding responsibilities, the client just need to know the interface of the class, the source code of the class can be remained to be unknown
- Responsibilities can be added to object not to the entire class
- The responsibilities and functionalities can be added without affecting other objects
- The added responsibilities and functionalities can be easily removed as they were added before
- Decorator adds objects at runtime as opposed to inheritance, where it must occur at compiling time
- A decorator is composed of a lot of little objects that make it really hard to debug
- It takes a little longer to instantiate an object if you also instantiate a Decorator
- Decorator may make a subject class overweight, when the class has involved many data or methods already.
- A decorator and its components are not identical, hence should not rely on object identity when apply decorators
- Always need to maintain the decorator's interface to be in sync with its components'
- XWindow use Decorators to add a title bar, border, and scroll bars to a window
- ImageVision uses Decorators to process image regions. It also uses them to cache processing requests. Decorators are the basis of the "pull" implementation of the Streams pattern
- ET++ uses Decorators to filter communication streams
- InterViews uses a Decorator to disable an interface element, by trapping and discarding input events. Decorators be used in other ways to restrict access to parts of an object, a firewall Decorator. InterViews uses a Decorator to echo requests for debugging interface elements
- Smart pointers are Decorators which count the number of references to an object
- A Monitor is a Decorator which automatically locks an object during a method call
![]() |
back |
up |
forward |
|
|
| ||||