結構型模式之擴充套件系統功能——裝飾模式
完整解決方案
為了讓系統具有更好的靈活性和可擴充套件性,克服繼承複用所帶來的問題,Sunny公司開發人員使用裝飾模式來重構圖形介面構件庫的設計,其中部分類的基本結構如圖所示:
在圖中,Component充當抽象構件類,其子類Window、TextBox、ListBox充當具體構件類,Component類的另一個子類ComponentDecorator充當抽象裝飾類,ComponentDecorator的子類ScrollBarDecorator和BlackBorderDecorator充當具體裝飾類。完整程式碼如下所示:
//抽象介面構件類:抽象構件類,為了突出與模式相關的核心程式碼,對原有控制元件程式碼進行了大改動abstract class Component { public abstract void display(); }
//窗體類:具體構件類 class Window extends Component { public void display() { System.out.println("顯示窗體!"); } }
//文字框類:具體構件類 class TextBox extends Component { public void display() { System.out.println("顯示文字框!"); } }
//列表框類:具體構件類 class ListBox extends Component { public void display() { System.out.println("顯示列表框!"); } }
//構件裝飾類:抽象裝飾類 class ComponentDecorator extends Component { private Component component; //維持對抽象構件型別物件的引用 public ComponentDecorator(Component component) //注入抽象構件 { this.component = component; } public void display() { component.display(); } }
//滾動條裝飾類:具體裝飾類 class ScrollBarDecorator extends ComponentDecorator { public ScrollBarDecorator(Component component) { super(component); } public void display() { this.setScrollBar(); super.display(); } public void setScrollBar() { System.out.println("為構件增加滾動條!"); } }
//黑色邊框裝飾類:具體裝飾類 class BlackBorderDecorator extends ComponentDecorator { public BlackBorderDecorator(Component component) { super(component); } public void display() { this.setBlackBorder(); super.display(); } public void setBlackBorder() { System.out.println("為構件增加黑色邊框!"); } }
編寫如下客戶端測試程式碼:
class Client { public static void main(String args[]) { Component component, componentSB; //使用抽象構件定義 component = new Window(); // 定義具體構件 componentSB = new ScrollBarDecorator(component); componentSB.display(); } }
編譯並執行程式,輸出結果如下:
為構件增加滾動條!
顯示窗體!
在客戶端程式碼中,我們先定義了一個Window型別的具體構件物件component,然後將component作為建構函式的引數注入到具體裝飾類ScrollBarDecorator中,得到一個裝飾之後物件componentSB,再呼叫componentSB的display()方法後將得到一個有滾動條的窗體。如果我們希望得到一個既有滾動條又有黑色邊框的窗體,不需要對原有類庫進行任何修改,只需將客戶端程式碼修改為如下所示:
class Client { public static void main(String args[]) { Component component, componentSB, componentBB; //全部使用 component = new Window(); componentSB = new ScrollBarDecorator(component); componentBB = new BlackBorderDecorator(componentSB); componentBB.display(); } }
編譯並執行程式,輸出結果如下:
為構件增加黑色邊框!
為構件增加滾動條!
顯示窗體!
我們可以將裝飾了一次之後的componentSB物件注入另一個裝飾類BlackBorderDecorator中實現第二次裝飾,得到一個經過兩次裝飾的物件componentBB,再呼叫componentBB的display()方法即可得到一個既有滾動條又有黑色邊框的窗體。
如果需要在原有系統中增加一個新的具體構件類或者新的具體裝飾類,無須修改現有類庫程式碼,只需將它們分別作為抽象構件類或者抽象裝飾類的子類即可。使用裝飾模式之後將大大減少了子類的個數,讓系統擴充套件起來更加方便,而且更容易維護,是取代繼承複用的有效方式之一。
裝飾模式總結
裝飾模式降低了系統的耦合度,可以動態增加或刪除物件的職責,並使得需要裝飾的具體構件類和具體裝飾類可以獨立變化,以便增加新的具體構件類和具體裝飾類。在軟體開發中, 裝飾模式應用較為廣泛,例如在JavaIO中的輸入流和輸出流的設計、javax.swing包中一些圖形介面構件功能的增強等地方都運用了裝飾模式。
裝飾模式的主要優點如下:
(1)對於擴充套件一個物件的功能,裝飾模式比繼承更加靈活性,不會導致類的個數急劇增加。
(2)可以通過一種動態的方式來擴充套件一個物件的功能,通過配置檔案可以在執行時選擇不同的具體裝飾類,從而實現不同的行為。
(3)可以對一個物件進行多次裝飾,通過使用不同的具體裝飾類以及這些裝飾類的排列組合, 可以創造出很多不同行為的組合,得到功能更為強大的物件。
(4)具體構件類與具體裝飾類可以獨立變化,使用者可以根據需要增加新的具體構件類和具體裝飾類,原有類庫程式碼無須改變,符合“開閉原則”。
裝飾模式的主要缺點如下:
(1)使用裝飾模式進行系統設計時將產生很多小物件,這些物件的區別在於它們之間相互連線的方式有所不同,而不是它們的類或者屬性值有所不同,大量小物件的產生勢必會佔用更多的系統資源,在一定程式上影響程式的效能。
(2) 裝飾模式提供了一種比繼承更加靈活機動的解決方案,但同時也意味著比繼承更加易於出錯,排錯也很困難,對於多次裝飾的物件,除錯時尋找錯誤可能需要逐級排查,較為繁瑣。
在以下情況下可以考慮使用裝飾模式:
(1)在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。
(2)當不能採用繼承的方式對系統進行擴充套件或者採用繼承不利於系統擴充套件和維護時可以使用裝飾模式。不能採用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴充套件,為支援每一種擴充套件或者擴充套件之間的組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因為類已定義為不能被繼承(如Java語言中的final類)。