設計模式的征途—10.裝飾(Decorator)模式

Edison Chou發表於2017-07-02

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

裝飾模式(Decorator) 學習難度:★★★☆☆ 使用頻率:★★★☆☆

一、圖形介面構件庫設計

1.1 需求背景

背景:M公司開發部基於OO技術開發了一套圖形介面構件庫Visual Component,該構件庫提供了大量的基本構件,如窗體、文字框、列表框等等,由於在使用該構件庫時,使用者經常要求定製一些特殊的顯示效果,例如帶滾動條的窗體,帶黑色邊框的文字框,即帶滾動條又帶黑色邊框的列表框等,因此經常需要對該構件庫進行擴充套件以增強其功能,如下圖所示:

  如何提高圖形介面構件庫的可擴充套件性並降低其維護成本是M公司開發部的程式猿們必須要面對的一個問題。

1.2 初始設計

  M公司的開發人員針對上面的需求,提出了一個基於繼承複用的初始設計方案,其基本結構如下圖所示:

  通過分析該設計方案,不難發現存在以下問題:

  (1)系統擴充套件麻煩,在C#/Java中根本無法實現(不支援多繼承)。

  (2)程式碼重複,不利於對系統進行修改和維護。

  (3)系統龐大,類的數量非常多。

  總之,這個設計不是一個好的設計方案,如何讓系統利於擴充套件又不導致類的數量線性增加呢?讓我們瞭解一下裝飾類把。

二、裝飾模式概述

2.1 裝飾模式簡介

  裝飾模式可以在不改變一個物件本身功能的基礎上給物件增加額外的新行為,在現實生活中,這種情況也到處存在,例如一張照片,可以不改變照片本身,給它增加一個相框,使得它具有防潮的功能,而且使用者可以根據需要給它增加不同型別的相框,甚至可以在一個小相框的外面再套一個大相框。

裝飾(Decorator)模式:動態地給一個物件增加一些額外的職責,就增加物件功能來說,裝飾模式遠比生成子類實現更加靈活。裝飾模式是一種物件結構型模式  

2.2 裝飾模式結構

  從結構圖中可以看出,裝飾模式主要有以下幾個角色:

  (1)Component (抽象構件):具體構件和抽象裝飾類的基類,宣告瞭在具體構建中實現的業務方法。

  (2)ConcreteComponent(具體構件):抽象構件的子類,用於定義具體的構件物件,實現了在抽象構件中宣告的方法,裝飾器可以給它增加額外的職責(方法)。

  (3)Decorator(抽象裝飾類):它也是抽象構件類的子類,用於給具體構件增加職責,但是具體職責在其子類中實現。

  (4)ConcreteDecorator(具體裝飾類):抽象裝飾類的子類,負責向構件新增新的職責。

三、重構圖形介面構件庫

3.1 重構後的設計方案

  為了讓系統具有更好的靈活性和可擴充套件性,克服繼承複用所帶來的問題,M公司開發人員使用裝飾模式來重構圖形介面庫的設計,其中部分類的基本結構如下圖所示:

  其中,Component充當抽象構件類,其子類Window、TextBox和ListBox充當具體構件類,ComponentDecorator則充當抽象裝飾類,ScrollBarDecorator和BlackBorderDecorator則充當具體裝飾類。

3.2 重構後的程式碼實現

  (1)抽象構件: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("顯示列表框!");
        }
    }

  (3)抽象裝飾:ComponentDecorator

    /// <summary>
    /// 構件裝飾類:抽象裝飾類
    /// </summary>
    public class ComponentDecorator : Component
    {
        private Component component;

        public ComponentDecorator (Component component)
        {
            this.component = component;
        }

        public override void Display()
        {
            component.Display();
        }
    }

  (4)具體裝飾: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 主要優點

  (1)對於擴充套件一個物件的功能,裝飾模式比繼承更加靈活 => 不會導致類的個數急劇增加!

  (2)可以對一個物件進行多次裝飾,從而創造出很多不同行為的組合 => 得到功能更為強大的物件!

  (3)具體構件類與具體裝飾類可以獨立變化,可以根據需要增加新的具體構建和具體裝飾 => 原有程式碼無需修改,符合開放封閉原則!

4.2 主要缺點

  雖然裝飾模式拱了一種比繼承更加靈活機動的方案,但同時也意味著比繼承更加易於出錯,排錯也很困難。特別是經過多次裝飾的物件,除錯時尋找錯誤可能需要逐級排查,較為繁瑣

4.3 應用場景

  (1)在不影響其他物件的情況下,想要動態地、透明地給單個物件新增職責 => 採用裝飾模式吧!

  (2)當不能採用繼承的方式對系統進行擴充套件 或 採取繼承不利於系統擴充套件和維護時 => 採用裝飾模式吧!

參考資料

  DesignPattern

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

 

相關文章