一、 裝飾(Decorator)模式
裝飾(Decorator)模式又名包裝(Wrapper)模式[GOF95]。裝飾模式以對客戶(hù)端透明的方式擴展對象的功能,是繼承關(guān)系的一個(gè)替代方案。
引言
孫悟空有七十二般變化,他的每一種變化都給他帶來(lái)一種附加的本領(lǐng)。他變成魚(yú)兒時(shí),就可以到水里游泳;他變成雀兒時(shí),就可以在天上飛行。而不管悟空怎么變化,在二郎神眼里,他永遠是那只猢猻。
裝飾模式以對客戶(hù)透明的方式動(dòng)態(tài)地給一個(gè)對象附加上更多的責任。換言之,客戶(hù)端并不會(huì )覺(jué)得對象在裝飾前和裝飾后有什么不同。裝飾模式可以在不使用創(chuàng )造更多子類(lèi)的情況下,將對象的功能加以擴展。
二、 裝飾模式的結構
裝飾模式使用原來(lái)被裝飾的類(lèi)的一個(gè)子類(lèi)的實(shí)例,把客戶(hù)端的調用委派到被裝飾類(lèi)。裝飾模式的關(guān)鍵在于這種擴展是完全透明的。
在孫猴子的例子里,老孫變成的魚(yú)兒相當于老孫的子類(lèi),這條魚(yú)兒與外界的互動(dòng)要通過(guò)"委派",交給老孫的本尊,由老孫本尊采取行動(dòng)。
裝飾模式的類(lèi)圖如下圖所示:
在裝飾模式中的各個(gè)角色有:
- 抽象構件(Component)角色:給出一個(gè)抽象接口,以規范準備接收附加責任的對象。
- 具體構件(Concrete Component)角色:定義一個(gè)將要接收附加責任的類(lèi)。
- 裝飾(Decorator)角色:持有一個(gè)構件(Component)對象的實(shí)例,并定義一個(gè)與抽象構件接口一致的接口。
- 具體裝飾(Concrete Decorator)角色:負責給構件對象"貼上"附加的責任。
三、 裝飾模式示例性代碼
以下示例性代碼實(shí)現了裝飾模式:
// 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()
{
}
}
/**////
/// Client test
/// 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();
}
}
上面的代碼在執行裝飾時(shí)是通過(guò)SetComponent方法實(shí)現的,在實(shí)際應用中,也有通過(guò)構造函數實(shí)現的,一個(gè)典型的創(chuàng )建過(guò)程可能如下:
new Decorator1(
new Decorator2(
new Decorator3(
new ConcreteComponent()
)
)
)
裝飾模式常常被稱(chēng)為包裹模式,就是因為每一個(gè)具體裝飾類(lèi)都將下一個(gè)具體裝飾類(lèi)或者具體構件類(lèi)包裹起來(lái)。
四、 裝飾模式應當在什么情況下使用
在以下情況下應當使用裝飾模式:
- 需要擴展一個(gè)類(lèi)的功能,或給一個(gè)類(lèi)增加附加責任。
- 需要動(dòng)態(tài)地給一個(gè)對象增加功能,這些功能可以再動(dòng)態(tài)地撤銷(xiāo)。
- 需要增加由一些基本功能的排列組合而產(chǎn)生的非常大量的功能,從而使繼承關(guān)系變得不現實(shí)。
五、 裝飾模式實(shí)際應用的例子
該例子演示了通過(guò)裝飾模式為圖書(shū)館的圖書(shū)與錄像帶添加"可借閱"裝飾。
// 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 );
}
}
/**////
/// DecoratorApp test
/// 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();
}
}
六、 使用裝飾模式的優(yōu)點(diǎn)和缺點(diǎn)
使用裝飾模式主要有以下的優(yōu)點(diǎn):
- 裝飾模式與繼承關(guān)系的目的都是要擴展對象的功能,但是裝飾模式可以提供比繼承更多的靈活性。
- 通過(guò)使用不同的具體裝飾類(lèi)以及這些裝飾類(lèi)的排列組合,設計師可以創(chuàng )造出很多不同行為的組合。
- 這種比繼承更加靈活機動(dòng)的特性,也同時(shí)意味著(zhù)裝飾模式比繼承更加易于出錯。
使用裝飾模式主要有以下的缺點(diǎn):
由于使用裝飾模式,可以比使用繼承關(guān)系需要較少數目的類(lèi)。使用較少的類(lèi),當然使設計比較易于進(jìn)行。但是,在另一方面,使用裝飾模式會(huì )產(chǎn)生比使用繼承關(guān)系更多的對象。更多的對象會(huì )使得查錯變得困難,特別是這些對象看上去都很相像。
七、 模式實(shí)現的討論
大多數情況下,裝飾模式的實(shí)現都比上面定義中給出的示意性實(shí)現要簡(jiǎn)單。對模式進(jìn)行簡(jiǎn)化時(shí)需要注意以下的情況:
(1)一個(gè)裝飾類(lèi)的接口必須與被裝飾類(lèi)的接口相容。
(2)盡量保持Component作為一個(gè)"輕"類(lèi),不要把太多的邏輯和狀態(tài)放在Component類(lèi)里。
(3)如果只有一個(gè)ConcreteComponent類(lèi)而沒(méi)有抽象的Component類(lèi)(接口),那么Decorator類(lèi)經(jīng)??梢允荂oncreteComponent的一個(gè)子類(lèi)。如下圖所示:
(4)如果只有一個(gè)ConcreteDecorator類(lèi),那么就沒(méi)有必要建立一個(gè)單獨的Decorator類(lèi),而可以把Decorator和ConcreteDecorator的責任合并成一個(gè)類(lèi)。
八、 透明性的要求
透明的裝飾模式
裝飾模式通常要求針對抽象編程。裝飾模式對客戶(hù)端的透明性要求程序不要聲明一個(gè)ConcreteDecorator類(lèi)型的變量,而應當聲明一個(gè)Component類(lèi)型的變量。換言之,下面的做法是對的:
Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator1(c);
Component c2 = new ConcreteDecorator(c1);
而下面的做法是不對的:
ConcreteComponent c = new ConcreteDecorator();
這就是前面所說(shuō)的,裝飾模式對客戶(hù)端是完全透明的含義。
用孫悟空的例子來(lái)說(shuō),必須永遠把孫悟空的所有變化都當成孫悟空來(lái)對待,而如果把老孫變成的雀兒當成雀兒,而不是老孫,那就被老孫騙了,而這是不應當發(fā)生的。
下面的做法是不對的:
大圣本尊 c = new 大圣本尊();
雀兒 bird = new 雀兒 (c);
半透明的裝飾模式
然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,增強所考慮的類(lèi)的性能。在增強性能的時(shí)候,往往需要建立新的公開(kāi)的方法。即便是在孫大圣的系統里,也需要新的方法。比如齊天大圣類(lèi)并沒(méi)有飛行的能力,而雀兒有。這就意味著(zhù)雀兒應當有一個(gè)新的fly()方法。
這就導致了大多數的裝飾模式的實(shí)現都是"半透明"(semi-transparent)的,而不是完全"透明"的。換言之,允許裝飾模式改變接口,增加新的方法。即聲明ConcreteDecorator類(lèi)型的變量,從而可以調用ConcreteDecorator類(lèi)中才有的方法:
齊天大圣 c = new 大圣本尊();
雀兒 bird = new 雀兒(c);
bird.fly();
齊天大圣接口根本沒(méi)有fly()這個(gè)方法,而雀兒接口里有這個(gè)方法。
九、 裝飾模式在.NET中的應用
.net中存在如下類(lèi)模型:
下面的代碼段用來(lái)將XmlDocument的內容格式輸出。我們可以體會(huì )Decorator模式在這里所起的作用。
// 生成ConcreteComponent(內存流ms)
MemoryStream ms = new MemoryStream();
// 用XmlTextWriter對內存流 ms 進(jìn)行裝飾
// 此處使用了半透明的裝飾模式
XmlTextWriter xtw = new XmlTextWriter(ms, Encoding.UTF8);
xtw.Formatting = Formatting.Indented;
// 對裝飾xtw的操作會(huì )轉而操作本體-內存流ms
xmlDoc.Save(xtw);
byte[] buf = ms.ToArray();
txtResult.Text = Encoding.UTF8.GetString(buf,0,buf.Length);
xtw.Close();