一、裝飾(Decorator)模式
裝飾(Decorator)模式又名包裝(Wrapper)模式[GOF95]。裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關系的一個替代方案。
引言
孫悟空有七十二般變化,他的每一種變化都給他帶來一種附加的本領。他變成魚兒時,就可以到水裡游泳;他變成雀兒時,就可以在天上飛行。而不管悟空怎麼變化,在二郎神眼裡,他永遠是那只猢狲。
裝飾模式以對客戶透明的方式動態地給一個對象附加上更多的責任。換言之,客戶端並不會覺得對象在裝飾前和裝飾後有什麼不同。裝飾模式可以在不使用創造更多子類的情況下,將對象的功能加以擴展。
二、裝飾模式的結構
裝飾模式使用原來被裝飾的類的一個子類的實例,把客戶端的調用委派到被裝飾類。裝飾模式的關鍵在於這種擴展是完全透明的。
在孫猴子的例子裡,老孫變成的魚兒相當於老孫的子類,這條魚兒與外界的互動要通過"委派",交給老孫的本尊,由老孫本尊采取行動。
裝飾模式的類圖如下圖所示:
在裝飾模式中的各個角色有:
抽象構件(Component)角色:給出一個抽象接口,以規范准備接收附加責任的對象。
具體構件(Concrete Component)角色:定義一個將要接收附加責任的類。
裝飾(Decorator)角色:持有一個構件(Component)對象的實例,並定義一個與抽象構件接口一致的接口。
具體裝飾(Concrete Decorator)角色:負責給構件對象"貼上"附加的責任。
三、裝飾模式示例性代碼
以下示例性代碼實現了裝飾模式:
// Decorator pattern -- Structural example
using System;
// "Component"
abstract class Component
{
// Methods
abstract public void Operation();
}
// "ConcreteComponent"
class ConcreteComponent : Component
{
// Methods
override public void Operation()
{
Console.WriteLine("ConcreteComponent.Operation()");
}
}
// "Decorator"
abstract class Decorator : Component
{
// Fields
protected Component component;
// Methods
public void SetComponent( Component component )
{
this.component = component;
}
override public void Operation()
{
if( component != null )
component.Operation();
}
}
// "ConcreteDecoratorA"
class ConcreteDecoratorA : Decorator
{
// Fields
private string addedState;
// Methods
override public void Operation()
{
base.Operation();
addedState = "new state";
Console.WriteLine("ConcreteDecoratorA.Operation()");
}
}
// "ConcreteDecoratorB"
class ConcreteDecoratorB : Decorator
{
// Methods
override public void Operation()
{
base.Operation();
AddedBehavior();
Console.WriteLine("ConcreteDecoratorB.Operation()");
}
void AddedBehavior()
{
}
}
/**//// <summary>
/// Client test
/// </summary>
public class Client
{
public static void Main( string[] args )
{
// Create ConcreteComponent and two Decorators
ConcreteComponent c = new ConcreteComponent();
ConcreteDecoratorA d1 = new ConcreteDecoratorA();
ConcreteDecoratorB d2 = new ConcreteDecoratorB();
// Link decorators
d1.SetComponent( c );
d2.SetComponent( d1 );
d2.Operation();
}
}
上面的代碼在執行裝飾時是通過SetComponent方法實現的,在實際應用中,也有通過構造函數實現的,一個典型的創建過程可能如下:
new Decorator1(
new Decorator2(
new Decorator3(
new ConcreteComponent()
)
)
)
裝飾模式常常被稱為包裹模式,就是因為每一個具體裝飾類都將下一個具體裝飾類或者具體構件類包裹起來。
四、裝飾模式應當在什麼情況下使用
在以下情況下應當使用裝飾模式:
需要擴展一個類的功能,或給一個類增加附加責任。
需要動態地給一個對象增加功能,這些功能可以再動態地撤銷。
需要增加由一些基本功能的排列組合而產生的非常大量的功能,從而使繼承關系變得不現實。
五、裝飾模式實際應用的例子
該例子演示了通過裝飾模式為圖書館的圖書與錄像帶添加"可借閱"裝飾。
// Decorator pattern -- Real World example
using System;
using System.Collections;
// "Component"
abstract class LibraryItem
{
// Fields
private int numCopies;
// Properties
public int NumCopies
{
get{ return numCopies; }
set{ numCopies = value; }
}
// Methods
public abstract void Display();
}
// "ConcreteComponent"
class Book : LibraryItem
{
// Fields
private string author;
private string title;
// Constructors
public Book(string author,string title,int numCopies)
{
this.author = author;
this.title = title;
this.NumCopies = numCopies;
}
// Methods
public override void Display()
{
Console.WriteLine( " Book ------ " );
Console.WriteLine( " Author: {0}", author );
Console.WriteLine( " Title: {0}", title );
Console.WriteLine( " # Copies: {0}", NumCopies );
}
}
// "ConcreteComponent"
class Video : LibraryItem
{
// Fields
private string director;
private string title;
private int playTime;
// Constructor
public Video( string director, string title,
int numCopies, int playTime )
{
this.director = director;
this.title = title;
this.NumCopies = numCopies;
this.playTime = playTime;
}
// Methods
public override void Display()
{
Console.WriteLine( " Video ----- " );
Console.WriteLine( " Director: {0}", director );
Console.WriteLine( " Title: {0}", title );
Console.WriteLine( " # Copies: {0}", NumCopies );
Console.WriteLine( " Playtime: {0}", playTime );
}
}
// "Decorator"
abstract class Decorator : LibraryItem
{
// Fields
protected LibraryItem libraryItem;
// Constructors
public Decorator ( LibraryItem libraryItem )
{ this.libraryItem = libraryItem; }
// Methods
public override void Display()
{ libraryItem.Display(); }
}
// "ConcreteDecorator"
class Borrowable : Decorator
{
// Fields
protected ArrayList borrowers = new ArrayList();
// Constructors
public Borrowable( LibraryItem libraryItem )
: base( libraryItem ) {}
// Methods
public void BorrowItem( string name )
{
borrowers.Add( name );
libraryItem.NumCopies--;
}
public void ReturnItem( string name )
{
borrowers.Remove( name );
libraryItem.NumCopies++;
}
public override void Display()
{
base.Display();
foreach( string borrower in borrowers )
Console.WriteLine( " borrower: {0}", borrower );
}
}
/**//// <summary>
/// DecoratorApp test
/// </summary>
public class DecoratorApp
{
public static void Main( string[] args )
{
// Create book and video and display
Book book = new Book( "Schnell", "My Home", 10 );
Video video = new Video( "Spielberg",
"Schindler's list", 23, 60 );
book.Display();
video.Display();
// Make video borrowable, then borrow and display
Console.WriteLine( " Video made borrowable:" );
Borrowable borrowvideo = new Borrowable( video );
borrowvideo.BorrowItem( "Cindy Lopez" );
borrowvideo.BorrowItem( "Samuel King" );
borrowvideo.Display();
}
}
六、使用裝飾模式的優點和缺點
使用裝飾模式主要有以下的優點:
裝飾模式與繼承關系的目的都是要擴展對象的功能,但是裝飾模式可以提供比繼承更多的靈活性。
通過使用不同的具體裝飾類以及這些裝飾類的排列組合,設計師可以創造出很多不同行為的組合。
這種比繼承更加靈活機動的特性,也同時意味著裝飾模式比繼承更加易於出錯。
使用裝飾模式主要有以下的缺點:
由於使用裝飾模式,可以比使用繼承關系需要較少數目的類。使用較少的類,當然使設計比較易於進行。但是,在另一方面,使用裝飾模式會產生比使用繼承關系更多的對象。更多的對象會使得查錯變得困難,特別是這些對象看上去都很相像。
七、模式實現的討論
大多數情況下,裝飾模式的實現都比上面定義中給出的示意性實現要簡單。對模式進行簡化時需要注意以下的情況:
(1)一個裝飾類的接口必須與被裝飾類的接口相容。
(2)盡量保持Component作為一個"輕"類,不要把太多的邏輯和狀態放在Component類裡。
(3)如果只有一個ConcreteComponent類而沒有抽象的Component類(接口),那麼Decorator類經常可以是ConcreteComponent的一個子類。如下圖所示:
(4)如果只有一個ConcreteDecorator類,那麼就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合並成一個類。
八、透明性的要求
透明的裝飾模式
裝飾模式通常要求針對抽象編程。裝飾模式對客戶端的透明性要求程序不要聲明一個ConcreteDecorator類型的變量,而應當聲明一個Component類型的變量。換言之,下面的做法是對的:
Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator1(c);
Component c2 = new ConcreteDecorator(c1);
而下面的做法是不對的:
ConcreteComponent c = new ConcreteDecorator();
這就是前面所說的,裝飾模式對客戶端是完全透明的含義。
用孫悟空的例子來說,必須永遠把孫悟空的所有變化都當成孫悟空來對待,而如果把老孫變成的雀兒當成雀兒,而不是老孫,那就被老孫騙了,而這是不應當發生的。
下面的做法是不對的:
大聖本尊 c = new 大聖本尊();
雀兒 bird = new 雀兒 (c);
半透明的裝飾模式
然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,增強所考慮的類的性能。在增強性能的時候,往往需要建立新的公開的方法。即便是在孫大聖的系統裡,也需要新的方法。比如齊天大聖類並沒有飛行的能力,而雀兒有。這就意味著雀兒應當有一個新的fly()方法。
這就導致了大多數的裝飾模式的實現都是"半透明"(semi-transparent)的,而不是完全"透明"的。換言之,允許裝飾模式改變接口,增加新的方法。即聲明ConcreteDecorator類型的變量,從而可以調用ConcreteDecorator類中才有的方法:
齊天大聖 c = new 大聖本尊();
雀兒 bird = new 雀兒(c);
bird.fly();
齊天大聖接口根本沒有fly()這個方法,而雀兒接口裡有這個方法。
九、裝飾模式在.NET中的應用
.net中存在如下類模型:
下面的代碼段用來將XmlDocument的內容格式輸出。我們可以體會Decorator模式在這裡所起的作用。
// 生成ConcreteComponent(內存流ms)
MemoryStream ms = new MemoryStream();
// 用XmlTextWriter對內存流 ms 進行裝飾
// 此處使用了半透明的裝飾模式
XmlTextWriter xtw = new XmlTextWriter(ms, Encoding.UTF8);
xtw.Formatting = Formatting.Indented;
// 對裝飾xtw的操作會轉而操作本體-內存流ms
xmlDoc.Save(xtw);
byte[] buf = ms.ToArray();
txtResult.Text = Encoding.UTF8.GetString(buf,0,buf.Length);
xtw.Close();