Design Pattern----10.Structural.Decorator.Pattern (Delphi Sample)

Intent

  • Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
  • Client-specified embellishment of a core object by recursively wrapping it.
  • Wrapping a gift, putting it in a box, and wrapping the box.

Problem

You want to add behavior or state to individual objects at run-time. Inheritance is not feasible because it is static and applies to an entire class.

Discussion

Suppose you are working on a user interface toolkit and you wish to support adding borders and scroll bars to windows. You could define an inheritance hierarchy like …

-Decorator scheme

 

But the Decorator pattern suggests giving the client the ability to specify whatever combination of “features” is desired.

  1:    Widget*  aWidget = new BorderDecorator(
  2:                         new HorizontalScrollBarDecorator(
  3:                           new VerticalScrollBarDecorator(
  4:                             new Window( 80, 24 ))));
  5:    aWidget->draw();
 

This flexibility can be achieved with the following design

Decorator scheme

 

Another example of cascading (or chaining) features together to produce a custom object might look like …

  1:   Stream*  aStream = new CompressingStream(
  2:                         new ASCII7Stream(
  3:                           new FileStream( "fileName.dat" )));
  4:    aStream->putString( "Hello world" );
  5: 

The solution to this class of problems involves encapsulating the original object inside an abstract wrapper interface. Both the decorator objects and the core object inherit from this abstract interface. The interface uses recursive composition to allow an unlimited number of decorator “layers” to be added to each core object.

 

Note that this pattern allows responsibilities to be added to an object, not methods to an object’s interface. The interface presented to the client must remain constant as successive layers are specified.

 

Also note that the core object’s identity has now been “hidden” inside of a decorator object. Trying to access the core object directly is now a problem.

Structure

The client is always interested in CoreFunctionality.doThis(). The client may, or may not, be interested in OptionalOne.doThis() and OptionalTwo.doThis(). Each of these classes always delegate to the Decorator base class, and that class always delegates to the contained “wrappee” object.

Decorator scheme

Example

The Decorator attaches additional responsibilities to an object dynamically. The ornaments that are added to pine or fir trees are examples of Decorators. Lights, garland, candy canes, glass ornaments, etc., can be added to a tree to give it a festive look. The ornaments do not change the tree itself which is recognizable as a Christmas tree regardless of particular ornaments used. As an example of additional functionality, the addition of lights allows one to “light up” a Christmas tree.

 

Although paintings can be hung on a wall with or without frames, frames are often added, and it is the frame which is actually hung on the wall. Prior to hanging, the paintings may be matted and framed, with the painting, matting, and frame forming a single visual component.

Decorator example

Check list

  1. Ensure the context is: a single core (or non-optional) component, several optional embellishments or wrappers, and an interface that is common to all.
  2. Create a “Lowest Common Denominator” interface that makes all classes interchangeable.
  3. Create a second level base class (Decorator) to support the optional wrapper classes.
  4. The Core class and Decorator class inherit from the LCD interface.
  5. The Decorator class declares a composition relationship to the LCD interface, and this data member is initialized in its constructor.
  6. The Decorator class delegates to the LCD object.
  7. Define a Decorator derived class for each optional embellishment.
  8. Decorator derived classes implement their wrapper functionality - and - delegate to the Decorator base class.
  9. The client configures the type and ordering of Core and Decorator objects.

Rules of thumb

  • Adapter provides a different interface to its subject. Proxy provides the same interface. Decorator provides an enhanced interface.
  • Adapter 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.
  • Composite and Decorator have similar structure diagrams, reflecting the fact that both rely on recursive composition to organize an open-ended number of objects.
  • 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.
  • 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.
  • 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.
  • 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.
  • Decorator lets you change the skin of an object. Strategy lets you change the guts.

Delphi Sample

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

 

Sometimes we want to add responsibilities to individual objects, not to an entire class. Suppose we have a family of classes used to output lines of text. The abstract base class TTextStream defines an interface, descendants like TTextFile, TLinePrinter and TClipboardStream implement this interface.

 

 

Now suppose we want to add behaviour to this family like buffering text, scrambling text and performing textual analysis while writing the text.

 

One way to add responsibilities is with inheritance. Inheriting a buffer from TTextStream will buffer output for every subclass instance. This is inflexible, however, because the choice of buffering is made statically. A client can’t control how and when to let the stream be buffered. Also, this loads the abstract class TTextStream with fields to control buffering which are carried by each instance. In general it is best to keep (abstract) base classes high up in the hierarchy as light weight as possible. Adding scrambling and textual analysis to the base class will make this class even heavier.

 

If we don’t want to create heavy weight base classes another problem arises. In this case a large number of independent extensions are possible and would produce an explosion of subclasses to support every combination: TBufTextFile, TScrambledTextFile, TBufScrambledTextFile, TBufLinePrinter, TScrambledLinePrinter etc. The same problem arises if a class definition is hidden or otherwise unavailable for subclassing. For example, if you want to add new behaviour to a class high up in a third party class library: try to add new behaviour to Delphi’s TStream class!

 

A more flexible approach is to enclose a text stream in another object that just adds buffering or scrambling. The enclosing object is called a decorator. The decorator conforms to the interface of the text stream it decorates so that it’s presence is transparent to the text stream’s clients. Conforming to an interface in Delphi implicates inheriting from a common ancestor, in this case TTextStream. The decorator forwards requests to the text stream it decorates and may perform additional actions (such as buffering or scrambling the text) before or after forwarding. Transparency lets you nest decorators recursively, thereby allowing an unlimited number of added independent responsibilities.

 

For example, suppose the interface of class TTextStream is:

  1: type 
  2:   TTextStream = class (TObject) 
  3:   protected 
  4:     function GetEndOfText: Boolean; virtual; abstract; 
  5:   public 
  6:     function ReadLine: string; virtual; abstract; 
  7:     procedure WriteLine(const Line: string); virtual; abstract; 
  8:     property EndOfText: Boolean read GetEndOfText; 
  9:   end;
 10: 

Using adapter patterns we could create real text streams, like TLinePrinter, TTextFile etc. conforming to this interface. Using the decorator pattern we can now add flexible functionality to all of these text streams. Suppose we name the decorator class TTextFilter. This class inherits from TTextStream which ensures the interface compliance. It also contains a reference to a TTextStream instance named TextStream. The class TTextFilter implements no new features, it simply passes on all requests (method calls) to the decorated class TextStream. Descendants like TIndentFilter and TUpperCaseFilter add behaviour by simply overriding decorated methods.

The following diagram shows how to compose a TTextStream object with a TUpperCaseFilter.

The important aspect of this pattern is that it lets decorators appear anywhere a TTextStream can appear. This way clients generally can’t tell the difference between a decorated component and an undecorated one, so they don’t depend at all on the decoration. In the example, the client doesn’t ‘know’ that text is converted to upper case before it is actually written.

Implementation

We’ll use the above described classes to demonstrate the implementation of a decorator pattern. In this example, TTextStream defines an (abstract) interface which is decorated by a TTextFilter class. Here’s the implementation:

  1: type 
  2:   TTextStream = class (TObject) 
  3:   protected 
  4:     function GetEndOfText: Boolean; virtual; abstract; 
  5:   public 
  6:     function ReadLine: string; virtual; abstract; 
  7:     procedure WriteLine(const Line: string); virtual; abstract; 
  8:     property EndOfText: Boolean read GetEndOfText; 
  9:   end; 
 10: 
 11:   TTextFilter = class (TTextStream) 
 12:   private 
 13:     FOwnsStream: Boolean; 
 14:     FTextStream: TTextStream; 
 15:   protected 
 16:     function GetEndOfText: Boolean; override; 
 17:     function GetTextStream: TTextStream; 
 18:     procedure SetTextStream(Value: TTextStream); 
 19:   public 
 20:     constructor Create(ATextStream: TTextStream; AOwnsStream: Boolean); 
 21:     destructor Destroy; override; 
 22:     function ReadLine: string; override; 
 23:     procedure WriteLine(const Line: string); override; 
 24:     property OwnsStream: Boolean read FOwnsStream write FOwnsStream; 
 25:     property TextStream: TTextStream read GetTextStream write SetTextStream; 
 26:   end; 
 27: 

In this interface, notice: The property TextStream which contains the reference to the decorated text stream. This property uses read and write access methods. This provides flexibility for descendants. A certain kind of proxy pattern, as described in, has the same structure as a decorator pattern. By using a read access method the pattern can be used to implement this kind of proxy pattern as well. The property OwnsStream which controls ownership of the property TextStream. You’ll see in the implementation that a TTextFilter will free an owned text stream if OwnsStream is set True. This helps in cleaning up structures using decorators. Both TextStream and OwnsStream are passed in the constructor Create. This is optional. The overridden methods ReadLine, WriteLine and GetEndOfText. These are the methods that implement the actual decoration.

 

Now let’s have a look at the implementation:

  1: constructor TTextFilter.Create(ATextStream: TTextStream; AOwnsStream: Boolean); 
  2: begin 
  3:   inherited Create; 
  4:   TextStream := ATextStream; 
  5:   OwnsStream := AOwnsStream; 
  6: end; 
  7: 
  8: destructor TTextFilter.Destroy; 
  9: begin 
 10:   TextStream := nil; 
 11:   inherited Destroy; 
 12: end; 
 13: 
 14: function TTextFilter.GetEndOfText: Boolean; 
 15: begin 
 16:   Result := TextStream.GetEndOfText; 
 17: end; 
 18: 
 19: function TTextFilter.GetTextStream: TTextStream; 
 20: begin 
 21:   Result := FTextStream; 
 22: end; 
 23: 
 24: function TTextFilter.ReadLine: string; 
 25: begin 
 26:   Result := TextStream.ReadLine; 
 27: end; 
 28: 
 29: procedure TTextFilter.SetTextStream(Value: TTextStream); 
 30: begin 
 31:   if Value <> FTextStream then 
 32:   begin 
 33:     if OwnsTextStream then FTextStream.Free; 
 34:     FTextStream := Value; 
 35:   end; 
 36: end; 
 37: 
 38: procedure TTextFilter.WriteLine(const Line: string); 
 39: begin 
 40:   TextStream.WriteLine(Line); 
 41: end; 
 42: 

Some interesting aspects in this implementation are: The decoration behaviour: methods ReadLine, WriteLine and GetEndOfText simply call the corresponding methods in TextStream. The SetTextStream method which takes care of actually freeing owned text streams before assigning a new value. The destructor Destroy uses this feature by setting TextStream := nil which will cause SetTextStream to free the current text stream if it’s owned.

 

It’s really easy to create a text filter converting text to uppercase now:

  1: 
  2: 
  3: type 
  4:   TUpperCaseFilter = class (TTextFilter) 
  5:   public 
  6:     function ReadLine: string; override; 
  7:     procedure WriteLine(const Line: string); override; 
  8:   end; 
  9: 
 10: implementation 
 11: 
 12: function TUpperCaseFilter.ReadLine: string; 
 13: begin 
 14:   Result := UpperCase(inherited ReadLine); 
 15: end; 
 16: 
 17: procedure TUpperCaseFilter.WriteLine(const Line: string); 
 18: begin 
 19:   inherited WriteLine(UpperCase(Line)); 
 20: end; 
 21: 

This filter could now be used to decorate any text stream target:

  1: 
  2: 
  3: function TClient.CreateOutput: TTextStream; 
  4: begin 
  5:   //create the base stream, depending on some setting
  6:   case Destination of 
  7:     dsFile: Result := TTextFile.Create(GetFileName, fmCreate); 
  8:     dsPrinter: Result := TLinePrinter.Create; 
  9:   end; 
 10:   //decide whether to use decorator or not, also depending on some setting 
 11:   //Note that it NOT important whether we decorate a LinePrinter or TextFile
 12:   if ConvertToUpperCase then 
 13:     Result := TUpperCaseFilter.Create(Result, True); 
 14: end; 
 15: 
 16: procedure TClient.ListContents; 
 17: var 
 18:   T: TTextStream; 
 19: begin 
 20:   T := CreateOutput; 
 21:   //At this point, we don't know if we're talking to a decorated output or not
 22:   try 
 23:     { list contents to T } 
 24:     T.WriteLine('Contents'); 
 25:   finally 
 26:     T.Free; 
 27:   end; 
 28: end; 
 29: 

It’s not spectacular, but it demonstrates the implementation and use of a decorator. You could imagine far more complex functionality to add using decorators, such as buffering, scrambling textual analysis etc.

decorator ['dekəreitə]

· n. 装饰者;室内装潢师

involve [in'vɔlv]

· vt. 包含;牵涉;使陷于;潜心于

recursive [ri'kə:siv]

· adj. [计]递归的;循环的

feasible ['fi:zəbl]

· adj. 可行的;可能的;可实行的

combination [,kɔmbi'neiʃən]

· n. 结合;化合;联合;组合

desire [di'zaiə]

· n. 欲望;要求,心愿;性欲

· vt. 要求;想要;希望得到…

· vi. 渴望

cascading [kæ'skeidiŋ]

· n. [电] 级联;串接;阶式渗透

· v. 瀑布般落下;串联;传递信息(cascade的ing形式)

posted on 2011-06-26 23:54 Tony Liu 阅读(...) 评论(...) 编辑 收藏

导航

公告