——.NET設計模式系列之十
Terrylee
,
2006
年
3
月
概述
在軟件系統(tǒng)中,有時候我們會使用繼承來擴展對象的功能,但是由于繼承為類型引入的靜態(tài)特質(zhì),使得這種擴展方式缺乏靈活性;并且隨著子類的增多(擴展功能的增多),各種子類的組合(擴展功能的組合)會導致更多子類的膨脹。如何使“對象功能的擴展”能夠根據(jù)需要來動態(tài)地實現(xiàn)?同時避免“擴展功能的增多”帶來的子類膨脹問題?從而使得任何“功能擴展變化”所導致的影響將為最低?這就是本文要講的
Decorator
模式。
意圖
動態(tài)地給一個對象添加一些額外的職責。就增加功能來說,
Decorator
模式相比生成子類更為靈活。
[GOF
《設計模式》
]
結(jié)構(gòu)圖
圖
1 Decorator
模式結(jié)構(gòu)圖
生活中的例子
裝飾模式動態(tài)地給一個對象添加額外的職責。不論一幅畫有沒有畫框都可以掛在墻上,但是通常都是有畫框的,并且實際上是畫框被掛在墻上。在掛在墻上之前,畫可以被蒙上玻璃,裝到框子里;這時畫、玻璃和畫框形成了一個物體。
圖
2
使用有畫框的畫作為例子的裝飾模式對象圖
裝飾模式解說
在軟件開發(fā)中,經(jīng)常會遇到動態(tài)地為一個對象而不是整個類增加一些功能的問題,還是以我慣用的記錄日志的例子來說明吧(也許在
Decorator
模式里面用這個例子不是特別合適)。現(xiàn)在要求我們開發(fā)的記錄日志的組件,除了要支持數(shù)據(jù)庫記錄
DatabaseLog
和文本文件記錄
TextFileLog
兩種方式外,我們還需要在不同的應用環(huán)境中增加一些額外的功能,比如需要記錄日志信息的錯誤嚴重級別,需要記錄日志信息的優(yōu)先級別,還有日志信息的擴展屬性等功能。在這里,如果我們不去考慮設計模式,解決問題的方法其實很簡單,可以通過繼承機制去實現(xiàn),日志類結(jié)構(gòu)圖如下:
圖
3
實現(xiàn)代碼如下:
public
abstract
class
Log
{
public
abstract
void
Write(
string
log);
}
public
class
DatabaseLog
:
Log
{
public
override
void
Write(
string
log)
{
//......
記錄到數(shù)據(jù)庫中
}
}
public
class
TextFileLog
:
Log
{
public
override
void
Write(
string
log)
{
//......
記錄到文本文件中
}
}
需要記錄日志信息的錯誤嚴重級別功能和記錄日志信息優(yōu)先級別的功能,只要在原來子類
DatabaseLog
和
TextFileLog
的基礎(chǔ)上再生成子類即可,同時需要引進兩個新的接口
IError
和
I
Priority
,類結(jié)構(gòu)圖如下:
圖
4
實現(xiàn)代碼如下:
public
interface
IError
{
void
SetError();
}
public
interface
IPriority
{
void
SetPriority();
}
public
class
DBErrorLog
:
DatabaseLog
,
IError
{
public
override
void
Write(
string
log)
{
base
.Write(log);
}
public
void
SetError()
{
//......
功能擴展,實現(xiàn)了記錄錯誤嚴重級別
}
}
public
class
DBPriorityLog
:
DatabaseLog
,
IPriority
{
public
override
void
Write(
string
log)
{
base
.Write(log);
}
public
void
SetPriority()
{
//......
功能擴展,實現(xiàn)了記錄優(yōu)先級別
}
}
public
class
TFErrorLog
:
TextFileLog
,
IError
{
public
override
void
Write(
string
log)
{
base
.Write(log);
}
public
void
SetError()
{
//......
功能擴展,實現(xiàn)了記錄錯誤嚴重級別
}
}
public
class
TFPriorityLog
:
TextFileLog
,
IPriority
{
public
override
void
Write(
string
log)
{
base
.Write(log);
}
public
void
SetPriority()
{
//......
功能擴展,實現(xiàn)了記錄優(yōu)先級別
}
}
此時可以看到,如果需要相應的功能,直接使用這些子類就可以了。這里我們采用了類的繼承方式來解決了對象功能的擴展問題,這種方式是可以達到我們預期的目的。然而,它卻帶來了一系列的問題。首先,前面的分析只是進行了一種功能的擴展,如果既需要記錄錯誤嚴重級別,又需要記錄優(yōu)先級時,子類就需要進行接口的多重繼承,這在某些情況下會違反類的單一職責原則,注意下圖中的藍色區(qū)域:
圖
5
實現(xiàn)代碼:
public
class
DBEPLog
:
DatabaseLog
,
IError
,
IPriority
{
public
override
void
Write(
string
log)
{
SetError();
SetPriority();
base
.Write(log);
}
public
void
SetError()
{
//......
功能擴展,實現(xiàn)了記錄錯誤嚴重級別
}
public
void
SetPriority()
{
//......
功能擴展,實現(xiàn)了記錄優(yōu)先級別
}
}
public
class
TFEPLog
:
DatabaseLog
,
IError
,
IPriority
{
public
override
void
Write(
string
log)
{
SetError();
SetPriority();
base
.Write(log);
}
public
void
SetError()
{
//......
功能擴展,實現(xiàn)了記錄錯誤嚴重級別
}
public
void
SetPriority()
{
//......
功能擴展,實現(xiàn)了記錄優(yōu)先級別
}
}
其次,隨著以后擴展功能的增多,子類會迅速的膨脹,可以看到,子類的出現(xiàn)其實是
DatabaseLog
和
TextFileLog
兩個子類與新增加的接口的一種排列組合關(guān)系,所以類結(jié)構(gòu)會變得很復雜而難以維護,正如象李建忠老師說的那樣“子類復子類,子類何其多”;最后,這種方式的擴展是一種靜態(tài)的擴展方式,并沒有能夠真正實現(xiàn)擴展功能的動態(tài)添加,客戶程序不能選擇添加擴展功能的方式和時機。
現(xiàn)在又該是
Decorator
模式出場的時候了,解決方案是把
Log
對象嵌入到另一個對象中,由這個對象來擴展功能。首先我們要定義一個抽象的包裝類
LogWrapper
,讓它繼承于
Log
類,結(jié)構(gòu)圖如下:
圖
6
實現(xiàn)代碼如下:
public
abstract
class
LogWrapper
:
Log
{
private
Log
_log;
public
LogWrapper(
Log
log)
{
_log = log;
}
public
override
void
Write(
string
log)
{
_log.Write(log);
}
}
現(xiàn)在對于每個擴展的功能,都增加一個包裝類的子類,讓它們來實現(xiàn)具體的擴展功能,如下圖中綠色的區(qū)域:
圖
7
實現(xiàn)代碼如下:
public
class
LogErrorWrapper
:
LogWrapper
{
public
LogErrorWrapper(
Log
_log)
:
base
(_log)
{
}
public
override
void
Write(
string
log)
{
SetError();
//......
功能擴展
base
.Write(log);
}
public
void
SetError()
{
//......
實現(xiàn)了記錄錯誤嚴重級別
}
}
public
class
LogPriorityWrapper
:
LogWrapper
{
public
LogPriorityWrapper(
Log
_log)
:
base
(_log)
{
}
public
override
void
Write(
string
log)
{
SetPriority();
//......
功能擴展
base
.Write(log);
}
public
void
SetPriority()
{
//......
實現(xiàn)了記錄優(yōu)先級別
}
}
到這里,
LogErrorWrapper
類和
LogPriorityWrapper
類真正實現(xiàn)了對錯誤嚴重級別和優(yōu)先級別的功能的擴展。我們來看一下客戶程序如何去調(diào)用它:
public
class
Program
{
public
static
void
Main(
string
[] args)
{
Log
log =
new
DatabaseLog
();
LogWrapper
lew1 =
new
LogErrorWrapper
(log);
//
擴展了記錄錯誤嚴重級別
lew1.Write(
"Log Message"
);
LogPriorityWrapper
lpw1 =
new
LogPriorityWrapper
(log);
//
擴展了記錄優(yōu)先級別
lpw1.Write(
"Log Message"
);
LogWrapper
lew2 =
new
LogErrorWrapper
(log);
LogPriorityWrapper
lpw2 =
new
LogPriorityWrapper
(lew2);
//
這里是lew2
//
同時擴展了錯誤嚴重級別和優(yōu)先級別
lpw2.Write(
"Log Message"
);
}
}
注意在上面程序中的第三段裝飾才真正體現(xiàn)出了
Decorator
模式的精妙所在,這里總共包裝了兩次:第一次對
log
對象進行錯誤嚴重級別的裝飾,變成了
lew2
對象,第二次再對
lew2
對象進行裝飾,于是變成了
lpw2
對象,此時的
lpw2
對象同時擴展了錯誤嚴重級別和優(yōu)先級別的功能。也就是說我們需要哪些功能,就可以這樣繼續(xù)包裝下去。到這里也許有人會說
LogPriorityWrapper
類的構(gòu)造函數(shù)接收的是一個Log對象,為什么這里可以傳入LogErrorWrapper對象呢?通過類結(jié)構(gòu)圖就能發(fā)現(xiàn),LogErrorWrapper類其實也是Log類的一個子類。
我們分析一下這樣會帶來什么好處?首先對于擴展功能已經(jīng)實現(xiàn)了真正的動態(tài)增加,只在需要某種功能的時候才進行包裝;其次,如果再出現(xiàn)一種新的擴展功能,只需要增加一個對應的包裝子類(注意:這一點任何時候都是避免不了的),而無需再進行很多子類的繼承,不會出現(xiàn)子類的膨脹,同時
Decorator
模式也很好的符合了面向?qū)ο笤O計原則中的“優(yōu)先使用對象組合而非繼承”和“開放
-
封閉”原則。
.NET
中的裝飾模式
1
.
.NET
中
Decorator
模式一個典型的運用就是關(guān)于
Stream
,它存在著如下的類結(jié)構(gòu):
圖
8
可以看到,
BufferedStream
和
CryptoStream
其實就是兩個包裝類,這里的
Decorator
模式省略了抽象裝飾角色(
Decorator
),示例代碼如下:
class
Program
{
public
static
void
Main(
string
[] args)
{
MemoryStream
ms =
new
MemoryStream
(
new
byte
[] { 100,456,864,222,567});
//
擴展了緩沖的功能
BufferedStream
buff =
new
BufferedStream
(ms);
//
擴展了緩沖,加密的功能
CryptoStream
crypto =
new
CryptoStream
(buff);
}
}
通過反編譯,可以看到
BufferedStream
類的代碼(只列出部分),它是繼承于
Stream
類:
public
sealed
class
BufferedStream
: Stream
{
// Methods
private
BufferedStream();
public
BufferedStream(Stream stream);
public
BufferedStream(Stream stream,
int
bufferSize);
// Fields
private
int
_bufferSize;
private
Stream _s;
}
2
.在
Enterprise Library
中的
DAAB
中有一個
DbCommandWrapper
的包裝類,它實現(xiàn)了對
IDbCommand
類的包裝并提供了參數(shù)處理的功能。結(jié)構(gòu)圖如下:
圖
9
示意性代碼如下:
public
abstract
class
DBCommandWrapper
:
MarshalByRefObject
,
IDisposable
{
}
public
class
SqlCommandWrapper
:
DBCommandWrapper
{
}
public
class
OracleCommandWrapper
:
DBCommandWrapper
{
}
效果及實現(xiàn)要點
1
.Component類在Decorator模式中充當抽象接口的角色,不應該去實現(xiàn)具體的行為。而且Decorator類對于Component類應該透明,換言之Component類無需知道Decorator類,Decorator類是從外部來擴展Component類的功能。
2
.Decorator類在接口上表現(xiàn)為is-a Component的繼承關(guān)系,即Decorator類繼承了Component類所具有的接口。但在實現(xiàn)上又表現(xiàn)為has-a Component的組合關(guān)系,即Decorator類又使用了另外一個Component類。我們可以使用一個或者多個Decorator對象來“裝飾”一個Component對象,且裝飾后的對象仍然是一個Component對象。
3
.Decortor模式并非解決“多子類衍生的多繼承”問題,Decorator模式的應用要點在于解決“主體類在多個方向上的擴展功能”——是為“裝飾”的含義。
4
.對于Decorator模式在實際中的運用可以很靈活。
如果只有一個ConcreteComponent類而沒有抽象的Component類,那么Decorator類可以是ConcreteComponent的一個子類。
圖10
如果只有一個ConcreteDecorator類,那么就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合并成一個類。
圖11
5
.Decorator模式的優(yōu)點是提供了比繼承更加靈活的擴展,
通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創(chuàng)造出很多不同行為的組合。
6
.由于使用裝飾模式,可以比使用繼承關(guān)系需要較少數(shù)目的類。使用較少的類,當然使設計比較易于進行。但是,在另一方面,使用裝飾模式會產(chǎn)生比使用繼承關(guān)系更多的對象。更多的對象會使得查錯變得困難,特別是這些對象看上去都很相像。
適用性
在以下情況下應當使用裝飾模式:
1.
需要擴展一個類的功能,或給一個類增加附加責任。
2.
需要動態(tài)地給一個對象增加功能,這些功能可以再動態(tài)地撤銷。
3.
需要增加由一些基本功能的排列組合而產(chǎn)生的非常大量的功能,從而使繼承關(guān)系變得不現(xiàn)實。
總結(jié)
Decorator
模式采用對象組合而非繼承的手法,實現(xiàn)了在運行時動態(tài)的擴展對象功能的能力,而且可以根據(jù)需要擴展多個功能,避免了單獨使用繼承帶來的“靈活性差”和“多子類衍生問題”。同時它很好地符合面向?qū)ο笤O計原則中“優(yōu)先使用對象組合而非繼承”和“開放
-
封閉”原則。
參考資料
閻宏,《
Java
與模式》,電子工業(yè)出版社
James W. Cooper
,《
C#
設計模式》,電子工業(yè)出版社
Alan Shalloway James R. Trott
,《
Design Patterns Explained
》,中國電力出版社
MSDN WebCast
《
C#
面向?qū)ο笤O計模式縱橫談
(10) Decorator
裝飾模式
(
結(jié)構(gòu)型模式
)
》
更多文章、技術(shù)交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
