1. 程式人生 > >設計模式的征途—10.裝飾(Decorator)模式

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

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

裝飾模式(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

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

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

設計模式征途10.裝飾Decorator模式

雖然目前房價依舊很高,就連我所在的成都郊區(非中心城區)的房價均價都早已破萬,但卻還是阻擋不了大家對新房的渴望和買房的熱情。如果大家買的是清水房,那麼無疑還有一項艱鉅的任務在等著大家,那就是裝修。對新房的裝修並沒有改變房屋用於居住的本質,但它可以讓房子變得更加漂亮和溫馨以及更加實用。在軟體設計中,也有一種類似

設計模式裝飾Decorator模式

isp 撤銷 有一個 inf 替代 接受 需要 裝飾著模式 () 設計模式之裝飾(Decorator)模式 (一)什麽是裝飾(Decorator)模式   裝飾模式,又稱為包裝模式,它以對客戶端透明的方式擴張對象的功能,是繼承關系的替代方案之一。  裝飾模式可以在不使用創造

設計模式裝飾Decorator模式

讓我 分享圖片 底部 .com 一件事 輸出 PE 新的 int 設計模式:裝飾器(Decorator)模式 一、前言 裝飾器模式也是一種非常重要的模式,在Java以及程序設計中占據著重要的地位。比如Java的數據流處理,我們可能看到數據流經過不同的類的包裝和包裹,最

設計模式裝飾Decorator模式

首先來看一個場景,如圖: 工人分為很多種類,比如電工,管道工等等,同時又有A公司的電工,B公司的電工,A公司的管道工,B公司的管道工等等,那麼當有M個工種和N個公司的時候,就會有 M * N 個子類,這個繼承體系就會變得很龐大和複雜。那麼如何簡化呢,那麼

設計模式征途—5.原型Prototype模式

pla width 共享 太多的 isp text 一模一樣 軟件 集合 相信大多數的人都看過《西遊記》,對孫悟空拔毛變出小猴子的故事情節應該都很熟悉。孫悟空可以用猴毛根據自己的形象復制出很多跟自己一模一樣的小猴兵出來,其實在設計模式中也有一個類似的模式,我們可以通過一個原

設計模式征途—16.訪問者Visitor模式

lose mar rtm image 3.1 conf 系統 .get 封裝性 在患者就醫時,醫生會根據病情開具處方單,很多醫院都會存在以下這個流程:劃價人員拿到處方單之後根據藥品名稱和數量計算總價,而藥房工作人員根據藥品名稱和數量準備藥品,如下圖所示。 在軟件開發中

設計模式征途—18.策略Strategy模式

滿足 應用 基礎 blog title pla 生成 display 多個 俗話說條條大路通羅馬,很多情況下實現某個目標地途徑都不只一條。在軟件開發中,也會時常遇到這樣的情況,實現某一個功能有多條途徑,每一條途徑都對應一種算法。此時,可以使用一種設計模式來實現靈活地選擇解決

設計模式征途—20.備忘錄Memento模式

行為 修煉之道 mda 3.2 ima 位置 pri 捕獲 spl 相信每個人都有後悔的時候,但是人生並無後悔藥,有些錯誤一旦發生就無法再挽回,有些事一旦錯過就不會再重來,有些話一旦說出口也就不可能再收回,這就是人生。為了不讓自己後悔,我們總是需要三思而後行。這裏我們要學習

設計模式征途—11.外觀Facade模式

在軟體開發中,有時候為了完成一項較為複雜的功能,一個類需要和多個其他業務類互動,而這些需要互動的業務類經常會作為一個完整的整體出現,由於涉及的類比較多,導致使用時程式碼較為複雜,此時,特別需要一個類似服務員一樣的角色,由他來負責和多個業務類進行互動,而使用這些業務類的類只需要和該類進行互動即可。外觀模式通過引

設計模式征途—13.代理Proxy模式

所謂代購,簡單說來就是找人幫忙購買所需要的商品。代購分為兩種型別,一種是因為在當地買不到某件商品,又或者是因為當地這件商品的價格比其他地區的貴,因此託人在其他地區甚至國外購買該商品,然後通過快遞發貨或直接攜帶回來。另一種則是消費者對想要購買的商品相關資訊的缺乏,自己無法確定其實際價值,因此只好委託中介講價或購

設計模式征途—19.命令Command模式

在生活中,我們裝修新房的最後幾道工序之一是安裝插座和開關,通過開關可以控制一些電器的開啟和關閉,例如電燈或換氣扇。在購買開關時,使用者並不知道它將來到底用於控制什麼電器,也就是說,開關與電燈、換氣扇並無直接關係,一個開關在安裝之後可能用來控制電燈,也可能用來控制換氣扇或者其他電器裝置。相同的開關可以通過不同的

設計模式征途—23.直譯器Interpreter模式

雖然目前計算機程式語言有好幾百種,但有時人們還是希望用一些簡單的語言來實現特定的操作,只需要向計算機輸入一個句子或檔案,就能按照預定的文法規則來對句子或檔案進行解釋。例如,我們想要只輸入一個加法/減法表示式,它就能夠計算出表示式結果。例如輸入“1+2+3-4+1”時,將輸出計算結果為3。像C++,Java或C

設計模式征途—7.介面卡Adapter模式

在現實生活中,我們的膝上型電腦的工作電壓大多數都是20V,而我國的家庭用電是220V,如何讓20V的膝上型電腦能夠工作在220V的電壓下工作?答案:引入一個電源介面卡,俗稱變壓器,有了這個電源介面卡,生活用電和膝上型電腦即可相容。 在軟體開發中,有時候也會存在這種不相容的情況,我們也可以像電源介面卡一樣引入

設計模式:觀察者Observer模式

image 強制轉換 trace vat PE sta obs observer -a 設計模式:觀察者(Observer)模式 一、前言 觀察者模式其實最好的名稱應該是“發布訂閱”模式,和我們現在大數據之中的發布訂閱方式比較類似,但是也有區別的地方,在上一個設計模式,

設計模式:享元FlyWeight模式

例子 清理 什麽 public == lean http 變量 -- 設計模式:享元(FlyWeight)模式 一、前言 享元(FlyWeight)模式顧名思義,既是輕量級的,原因就是享元,共享元素,這裏的元素指的是對象。如何共享對象,那就是在檢測對象產生的時候,如

Koffee設計模式學習之路 —— 模式學習總結思路

    這篇部落格沒有相關技術細節,僅作為自己對設計模式這個東西的一點感悟和以後設計模式系列部落格的一個寫作思路。     作為非科班出身,誤打誤撞進入程式設計的人,在上研究生期間對於程式的唯一要求就是:能用。彼時,不知道有面向物件,記憶體管理,多執行緒,

【java設計模式】之 代理Proxy模式

代理模式的核心作用就是通過代理,控制對物件的訪問。這跟實際中是一樣的,比如說明星都有經紀人,這就是一個代理,比如有人要找某明星拍戲,那麼首先處理這事的是他的經紀人,雖然拍戲需要自己拍,但是拍戲前後的一些必須要做的事等等,都由這個經紀人來處理。    在程式中也是如此,通過

有關模式窗體和無模式窗體的區別

最小化 調用方法 關閉 對話 一起 兩種模式 獲得 windows new   在客戶端和網頁的開發過程中,我們都會或多或少遇到過模式窗體和無(非)模式窗體(以下簡稱無模式窗體),   在傳統的Windows窗體開發過程中,對話框有兩種模式:模式窗體和無模式窗體。   模式

設計模式裝飾模式Decorator

設計模式之裝飾模式(Decorator) 場景 普通人跑步,跳高,舉重很普通。 現在需要使用裝備變成鋼鐵俠,3項指標成倍擴大 Man介面 public interface Man { void run(); int highJump();

設計模式7—— 結構型 —— 裝飾Decorator

介紹 定義:在不改變原有物件基礎之上,將功能附加到物件上 說明:在擴充套件原有物件功能方面,提供了比繼承更有彈性的替代方案 型別:結構型 適用場景: 擴充套件一個類的功能或給一個類新增附加職責 動態地給一個