設計模式的征途—10.裝飾(Decorator)模式
雖然目前房價依舊很高,就連我所在的成都郊區(非中心城區)的房價均價都早已破萬,但卻還是阻擋不了大家對新房的渴望和買房的熱情。如果大家買的是清水房,那么無疑還有一項艱巨的任務在等著大家,那就是裝修。對新房的裝修并沒有改變房屋用于居住的本質,但它可以讓房子變得更加漂亮和溫馨以及更加實用。在軟件設計中,也有一種類似于新房裝修的技術可以對已有的功能進行擴展使之更加符合用戶需求,從而使得對象具有更加強大的功能,這便是本次即將介紹的裝飾模式。
| 裝飾模式(Decorator) | 學習難度:★★★☆☆ | 使用頻率:★★★☆☆ |
一、圖形界面構件庫設計
1.1 需求背景
背景:M公司開發部基于OO技術開發了一套圖形界面構件庫Visual Component,該構件庫提供了大量的基本構件,如窗體、文本框、列表框等等,由于在使用該構件庫時,用戶經常要求定制一些特殊的顯示效果,例如帶滾動條的窗體,帶黑色邊框的文本框,即帶滾動條又帶黑色邊框的列表框等,因此經常需要對該構件庫進行擴展以增強其功能,如下圖所示:
如何提高圖形界面構件庫的可擴展性并降低其維護成本是M公司開發部的程序猿們必須要面對的一個問題。
1.2 初始設計
M公司的開發人員針對上面的需求,提出了一個基于繼承復用的初始設計方案,其基本結構如下圖所示:

通過分析該設計方案,不難發現存在以下問題:
(1)系統擴展麻煩,在C#/Java中根本無法實現(不支持多繼承)。
?。?)代碼重復,不利于對系統進行修改和維護。
?。?)系統龐大,類的數量非常多。
總之,這個設計不是一個好的設計方案,如何讓系統利于擴展又不導致類的數量線性增加呢?讓我們了解一下裝飾類把。
二、裝飾模式概述
2.1 裝飾模式簡介
裝飾模式可以在不改變一個對象本身功能的基礎上給對象增加額外的新行為,在現實生活中,這種情況也到處存在,例如一張照片,可以不改變照片本身,給它增加一個相框,使得它具有防潮的功能,而且用戶可以根據需要給它增加不同類型的相框,甚至可以在一個小相框的外面再套一個大相框。
裝飾(Decorator)模式:動態地給一個對象增加一些額外的職責,就增加對象功能來說,裝飾模式遠比生成子類實現更加靈活。裝飾模式是一種對象結構型模式
2.2 裝飾模式結構

從結構圖中可以看出,裝飾模式主要有以下幾個角色:
?。?)Component (抽象構件):具體構件和抽象裝飾類的基類,聲明了在具體構建中實現的業務方法。
?。?)ConcreteComponent(具體構件):抽象構件的子類,用于定義具體的構件對象,實現了在抽象構件中聲明的方法,裝飾器可以給它增加額外的職責(方法)。
?。?)Decorator(抽象裝飾類):它也是抽象構件類的子類,用于給具體構件增加職責,但是具體職責在其子類中實現。
?。?)ConcreteDecorator(具體裝飾類):抽象裝飾類的子類,負責向構件添加新的職責。
三、重構圖形界面構件庫
3.1 重構后的設計方案
為了讓系統具有更好的靈活性和可擴展性,克服繼承復用所帶來的問題,M公司開發人員使用裝飾模式來重構圖形界面庫的設計,其中部分類的基本結構如下圖所示:

其中,Component充當抽象構件類,其子類Window、TextBox和ListBox充當具體構件類,ComponentDecorator則充當抽象裝飾類,ScrollBarDecorator和BlackBorderDecorator則充當具體裝飾類。
3.2 重構后的代碼實現
?。?)抽象構件:Component
/// <summary> /// 抽象界面構件類:抽象構件類 /// </summary> public abstract class Component { public abstract void Display(); }
(2)具體構件:Window, TextBox 和 ListBox
/// <summary> /// 窗體類:具體構件類 /// </summary> public class Window : Component { public override void Display() { Console.WriteLine("顯示窗體!"); } } /// <summary> /// 文本框類:具體構件類 /// </summary> public class TextBox : Component { public override void Display() { Console.WriteLine("顯示文本框!"); } } /// <summary> /// 列表框類:具體構件類 /// </summary> public class ListBox : Component { public override void Display() { Console.WriteLine("顯示列表框!"); } }
?。?)抽象裝飾:ComponentDecorator
/// <summary> /// 構件裝飾類:抽象裝飾類 /// </summary> public class ComponentDecorator : Component { private Component component; public ComponentDecorator (Component component) { this.component = component; } public override void Display() { component.Display(); } }
?。?)具體裝飾:ScrollBarDecorator 和 BlackBorderDecorator
/// <summary> /// 滾動條裝飾類:具體裝飾類 /// </summary> public class ScrollBarDecorator : ComponentDecorator { public ScrollBarDecorator(Component component) : base(component) { } public override void Display() { this.SetScrollBar(); base.Display(); } public void SetScrollBar() { Console.WriteLine("為構件增加滾動條!"); } } /// <summary> /// 黑色邊框裝飾類:具體裝飾類 /// </summary> public class BlackBorderDecorator : ComponentDecorator { public BlackBorderDecorator(Component component) : base(component) { } public override void Display() { this.SetScrollBar(); base.Display(); } public void SetScrollBar() { Console.WriteLine("為構件增加黑色邊框!"); } }
(5)客戶端測試
public class Program { public static void Main(string[] args) { Component component = new Window(); // 一次裝飾 Component componentSB = new ScrollBarDecorator(component); componentSB.Display(); Console.WriteLine(); // 二次裝飾 Component componentBB = new BlackBorderDecorator(componentSB); componentBB.Display(); Console.ReadKey(); } }
執行后的結果如下圖所示:

可以看到,第一次裝飾之后,窗體有了滾動條。第二次裝飾之后,窗體不僅有了滾動條,還增加了黑色邊框。
四、裝飾模式小結
4.1 主要優點
?。?)對于擴展一個對象的功能,裝飾模式比繼承更加靈活 => 不會導致類的個數急劇增加!
?。?)可以對一個對象進行多次裝飾,從而創造出很多不同行為的組合 => 得到功能更為強大的對象!
?。?)具體構件類與具體裝飾類可以獨立變化,可以根據需要增加新的具體構建和具體裝飾 => 原有代碼無需修改,符合開放封閉原則!
4.2 主要缺點
雖然裝飾模式拱了一種比繼承更加靈活機動的方案,但同時也意味著比繼承更加易于出錯,排錯也很困難。特別是經過多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較為繁瑣。
4.3 應用場景
?。?)在不影響其他對象的情況下,想要動態地、透明地給單個對象添加職責 => 采用裝飾模式吧!
?。?)當不能采用繼承的方式對系統進行擴展 或 采取繼承不利于系統擴展和維護時 => 采用裝飾模式吧!
參考資料

劉偉,《設計模式的藝術—軟件開發人員內功修煉之道》

雖然目前房價依舊很高,就連我所在的成都郊區(非中心城區)的房價均價都早已破萬,但卻還是阻擋不了大家對新房的渴望和買房的熱情。如果大家買的是清水房,那么無疑還有一項艱巨的任務在等著大家,那就是裝修。對新房的裝修并沒有改變房屋用于居住的本質,但它可以讓房子變得更加漂亮和溫馨以及更加實用。在軟件設計中,也有一種類似于新房裝修的技術可以對已有的功能進行擴展使之更加符合用戶需求,從而使得對象具有更加強大的功能,這便是本次即將介紹的裝飾模式。


浙公網安備 33010602011771號