一、 裝飾(Decorator)模式
裝飾(Decorator)模式又名包裝(Wrapper)模式[GOF95]。裝飾模式以對(duì)客戶端透明的方式擴(kuò)展對(duì)象的功能,是繼承關(guān)系的一個(gè)替代方案。
引言
孫悟空有七十二般變化,他的每一種變化都給他帶來一種附加的本領(lǐng)。他變成魚兒時(shí),就可以到水里游泳;他變成雀兒時(shí),就可以在天上飛行。而不管悟空怎么變化,在二郎神眼里,他永遠(yuǎn)是那只猢猻。
裝飾模式以對(duì)客戶透明的方式動(dòng)態(tài)地給一個(gè)對(duì)象附加上更多的責(zé)任。換言之,客戶端并不會(huì)覺得對(duì)象在裝飾前和裝飾后有什么不同。裝飾模式可以在不使用創(chuàng)造更多子類的情況下,將對(duì)象的功能加以擴(kuò)展。
二、 裝飾模式的結(jié)構(gòu)
裝飾模式使用原來被裝飾的類的一個(gè)子類的實(shí)例,把客戶端的調(diào)用委派到被裝飾類。裝飾模式的關(guān)鍵在于這種擴(kuò)展是完全透明的。
在孫猴子的例子里,老孫變成的魚兒相當(dāng)于老孫的子類,這條魚兒與外界的互動(dòng)要通過"委派",交給老孫的本尊,由老孫本尊采取行動(dòng)。
裝飾模式的類圖如下圖所示:

在裝飾模式中的各個(gè)角色有:
- 抽象構(gòu)件(Component)角色:給出一個(gè)抽象接口,以規(guī)范準(zhǔn)備接收附加責(zé)任的對(duì)象。
- 具體構(gòu)件(Concrete Component)角色:定義一個(gè)將要接收附加責(zé)任的類。
- 裝飾(Decorator)角色:持有一個(gè)構(gòu)件(Component)對(duì)象的實(shí)例,并定義一個(gè)與抽象構(gòu)件接口一致的接口。
- 具體裝飾(Concrete Decorator)角色:負(fù)責(zé)給構(gòu)件對(duì)象"貼上"附加的責(zé)任。
三、 裝飾模式示例性代碼
以下示例性代碼實(shí)現(xiàn)了裝飾模式:
// 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();
}
}
上面的代碼在執(zhí)行裝飾時(shí)是通過SetComponent方法實(shí)現(xiàn)的,在實(shí)際應(yīng)用中,也有通過構(gòu)造函數(shù)實(shí)現(xiàn)的,一個(gè)典型的創(chuàng)建過程可能如下:
new Decorator1(
new Decorator2(
new Decorator3(
new ConcreteComponent()
)
)
)
裝飾模式常常被稱為包裹模式,就是因?yàn)槊恳粋€(gè)具體裝飾類都將下一個(gè)具體裝飾類或者具體構(gòu)件類包裹起來。
四、 裝飾模式應(yīng)當(dāng)在什么情況下使用
在以下情況下應(yīng)當(dāng)使用裝飾模式:
- 需要擴(kuò)展一個(gè)類的功能,或給一個(gè)類增加附加責(zé)任。
- 需要?jiǎng)討B(tài)地給一個(gè)對(duì)象增加功能,這些功能可以再動(dòng)態(tài)地撤銷。
- 需要增加由一些基本功能的排列組合而產(chǎn)生的非常大量的功能,從而使繼承關(guān)系變得不現(xiàn)實(shí)。
五、 裝飾模式實(shí)際應(yīng)用的例子
該例子演示了通過裝飾模式為圖書館的圖書與錄像帶添加"可借閱"裝飾。
// 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)系的目的都是要擴(kuò)展對(duì)象的功能,但是裝飾模式可以提供比繼承更多的靈活性。
- 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,設(shè)計(jì)師可以創(chuàng)造出很多不同行為的組合。
- 這種比繼承更加靈活機(jī)動(dòng)的特性,也同時(shí)意味著裝飾模式比繼承更加易于出錯(cuò)。
使用裝飾模式主要有以下的缺點(diǎn):
由于使用裝飾模式,可以比使用繼承關(guān)系需要較少數(shù)目的類。使用較少的類,當(dāng)然使設(shè)計(jì)比較易于進(jìn)行。但是,在另一方面,使用裝飾模式會(huì)產(chǎn)生比使用繼承關(guān)系更多的對(duì)象。更多的對(duì)象會(huì)使得查錯(cuò)變得困難,特別是這些對(duì)象看上去都很相像。
七、 模式實(shí)現(xiàn)的討論
大多數(shù)情況下,裝飾模式的實(shí)現(xiàn)都比上面定義中給出的示意性實(shí)現(xiàn)要簡(jiǎn)單。對(duì)模式進(jìn)行簡(jiǎn)化時(shí)需要注意以下的情況:
(1)一個(gè)裝飾類的接口必須與被裝飾類的接口相容。
(2)盡量保持Component作為一個(gè)"輕"類,不要把太多的邏輯和狀態(tài)放在Component類里。
(3)如果只有一個(gè)ConcreteComponent類而沒有抽象的Component類(接口),那么Decorator類經(jīng)??梢允荂oncreteComponent的一個(gè)子類。如下圖所示:

(4)如果只有一個(gè)ConcreteDecorator類,那么就沒有必要建立一個(gè)單獨(dú)的Decorator類,而可以把Decorator和ConcreteDecorator的責(zé)任合并成一個(gè)類。
八、 透明性的要求
透明的裝飾模式
裝飾模式通常要求針對(duì)抽象編程。裝飾模式對(duì)客戶端的透明性要求程序不要聲明一個(gè)ConcreteDecorator類型的變量,而應(yīng)當(dāng)聲明一個(gè)Component類型的變量。換言之,下面的做法是對(duì)的:
Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator1(c);
Component c2 = new ConcreteDecorator(c1);
而下面的做法是不對(duì)的:
ConcreteComponent c = new ConcreteDecorator();
這就是前面所說的,裝飾模式對(duì)客戶端是完全透明的含義。
用孫悟空的例子來說,必須永遠(yuǎn)把孫悟空的所有變化都當(dāng)成孫悟空來對(duì)待,而如果把老孫變成的雀兒當(dāng)成雀兒,而不是老孫,那就被老孫騙了,而這是不應(yīng)當(dāng)發(fā)生的。
下面的做法是不對(duì)的:
大圣本尊 c = new 大圣本尊();
雀兒 bird = new 雀兒 (c);
半透明的裝飾模式
然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,增強(qiáng)所考慮的類的性能。在增強(qiáng)性能的時(shí)候,往往需要建立新的公開的方法。即
便是在孫大圣的系統(tǒng)里,也需要新的方法。比如齊天大圣類并沒有飛行的能力,而雀兒有。這就意味著雀兒應(yīng)當(dāng)有一個(gè)新的fly()方法。
這就導(dǎo)致了大多數(shù)的裝飾模式的實(shí)現(xiàn)都是"半透明"(semi-transparent)的,而不是完全"透明"的。換言之,允許裝飾模式改變接口,
增加新的方法。即聲明ConcreteDecorator類型的變量,從而可以調(diào)用ConcreteDecorator類中才有的方法:
齊天大圣 c = new 大圣本尊();
雀兒 bird = new 雀兒(c);
bird.fly();
齊天大圣接口根本沒有fly()這個(gè)方法,而雀兒接口里有這個(gè)方法。
九、 裝飾模式在.NET中的應(yīng)用
.net中存在如下類模型:

下面的代碼段用來將XmlDocument的內(nèi)容格式輸出。我們可以體會(huì)Decorator模式在這里所起的作用。
// 生成ConcreteComponent(內(nèi)存流ms)
MemoryStream ms = new MemoryStream();

// 用XmlTextWriter對(duì)內(nèi)存流 ms 進(jìn)行裝飾
// 此處使用了半透明的裝飾模式
XmlTextWriter xtw = new XmlTextWriter(ms, Encoding.UTF8);
xtw.Formatting = Formatting.Indented;

// 對(duì)裝飾xtw的操作會(huì)轉(zhuǎn)而操作本體-內(nèi)存流ms
xmlDoc.Save(xtw);

byte[] buf = ms.ToArray();
txtResult.Text = Encoding.UTF8.GetString(buf,0,buf.Length);
xtw.Close();
|