解讀設計模式----裝飾模式(Decorator Pattern)
UML圖:
一、使用Decorator模式的動機
現在有這樣一個場景,要求我們爲一個對象動態添加新的職責,這個職責並不修改原有的行爲,而是在原有行爲的基礎上添加新的功能,就好比我們在吃薯條的時候塗上新鮮美味的番茄汁一般。
從面向對象的角度來說,我們要爲一個對象添加一新的職責完全可以利用繼承機制來實現,但是這樣的設計會導致一個問題,“過度地使用繼承來擴展對象的功能”由於繼承爲類型引入的靜態特質,使得這種擴展方式缺乏靈活性;並且隨着字類的增多(擴展功能的增多),各種子類的組合(擴展功能的組合)會導致更多子類的膨脹,也就是所謂的類爆炸。如何使“對象功能的擴展”能夠根據需要來動態地實現同時又避免“擴展功能的增加”帶來的類爆炸問題?從而使得任何“功能擴展變化”所導致的影響將爲最低?
二、迎接Decorator的到來
例如我們要爲一隻“筆”來設計其行爲。從我們擁有的面向對象的知識出發,爲一個對象的原有方法添加新的職責,可以通過繼承機制,從寫基類方法來實現,此時父類方法應爲虛方法或是抽象方法。
2{
3 public class Pen
4 {
5 public virtual string Write()
6 {
7 return "普通的筆"; //只能進行最基本的寫操作
8 }
9 }
10}
如果我們需要爲該筆添加新的職責,讓其可以調整字體大小(這樣的示例好象有點不符合現實哈,此處只是通過這個虛有的對象來演示Decorator,暫不考慮現實問題),則可以定義一子類繼承於Pen,然後重寫Write方法。
2{
3
4 public class BoldPen : Pen
5 {
6 public override string Write()
7 {
8 return base.Write() + FontSize();
9 }
10
11 private string FontSize()
12 {
13 return "字體大小:10px";
14 }
15 }
16}
以上的實際完全符合裝飾模式的意圖“不改變原有的行爲動態地給一個對象添加一些新的功能”。想想,這樣的設計美好嗎?現在需求改變,要求這種筆出了能寫之外同時還擁有可以設置字體大小及字體顏色的設置功能,按繼承機制的思想來設計是不是應該繼承BlodPen然後又重寫Write方法呢?這樣下去就形成了一個多子類的延伸的多重繼承體系,最終出現的問題就是類無限的增多,既所謂的類爆炸。
三、重構Decorator的設計
要解決上述出現類爆炸的問題該怎麼辦呢?仔細觀察就會發現,通過繼承子類在添加新的職責的時候都需要重寫Write方法才能實現,那我們是不是應該重構一下,抽象出共性呢?答案是肯定的,我們完全可以把Write方法抽象爲接口或抽象類,通過抽象後我們可以通過聚合的方法動態的組合新的功能職責。重構後的設計如下:
接口的抽象:
2{
3 public interface IWrite
4 {
5 string Write();
6 }
7}
8
Pen類的設計:
2{
3 /// <summary>
4 /// 實現IWrite接口
5 /// </summary>
6 public class Pen:IWrite
7 {
8 public string Write()
9 {
10 return "能寫的筆";
11 }
12 }
13}
14
BoldPen類的設計:
2{
3 public class BoldPen:IWrite
4 {
5 IWrite component = null;
6 int borderWidth = 0;
7
8 public BoldPen() { }
9
10 public BoldPen(IWrite compontent, int borderWidth)
11 {
12 this.component = compontent;
13 this.borderWidth = borderWidth;
14 }
15
16 /// <summary>
17 /// 接口方法
18 /// </summary>
19 public string Write()
20 {
21 //調用接口方法
22 string pen = this.component.Write();
23 return pen + " 字體大小:" + this.borderWidth.ToString();
24 }
25
26 /// <summary>
27 /// 裝飾方法
28 /// </summary>
29 /// <param name="borderWidth"></param>
30 public void SetBorderWidth(int borderWidth)
31 {
32 this.borderWidth = borderWidth;
33 }
34 }
35}
通過共性的抽象和一系列的新功能職責的裝飾,新的設計應運而生,現在如果我們需要擴展出一種新的筆種,要求出了具備基本的寫行爲之外,還同時帶有可設置字體大小和字體顏色的功能。顯然,我們之前的設計就派上了用場,這時我們只需要通過聚合的方法就能夠組合成這一種新的角色(筆種)出來。很明顯應用了Decorator讓設計變得更加靈活,同時也說明了一個問題,在我們實際的設計中,應少用繼承多,儘量的通過聚合的方法來設計,提高設計靈活度。
通過組合設計出的新的筆種:
2{
3 public class BoldColorPen:IWrite
4 {
5 private List<IWrite> list=null;
6 public BoldColorPen()
7 { }
8
9 public BoldColorPen(List<IWrite> list)
10 {
11 this.list = list;
12 }
13 public string Write()
14 {
15 string str = string.Empty;
16 for (int i = 0; i < list.Count; i++)
17 {
18 str += ((IWrite)list[i]).Write() + " ";
19 }
20 return str;
21 }
22
23 private string Other()
24 {
25 //同樣我們還可爲其添加自己的職責
26 }
27 }
28}
29
四、完善Decorator的設計
此部分我就不多說了,代碼也已經在上面展示出。示例運行結果:
五、Decorator模式的要點
通過採用組合,而非繼承手法,Decorator模式實現了在運行時動態地擴展對象功能的行爲,而且可以根據需要擴展多個功能,避免了單獨使用繼承所帶來“靈活性差”和“類爆炸”等問題。把不同的職責封裝在不同的職責類的私有方法或屬性中,這樣對內開放,對外封閉。符合面向對象的“單一職責”和“開放--封閉”原則;同時也很好的符合面向對象設計原則中的“優先使用對象組合而非繼承”。
六、本文參考資料
張逸 《軟件設計精要與模式》 電子工業出版社
MSDN WebCast 《C#面向對象設計模式縱橫談(10) Decorator裝飾模式(結構型模式)》