Head First設計模式之裝飾者模式(Decorator Pattern)

前言:編程

     本節將深度討論繼承濫用問題,將會學到使用對象組合的方式,在運行時裝飾類,在不修改任何底層代碼的狀況下,給對象賦予新的職責。ide

1.    基本需求:咖啡連鎖店業務擴張須要從新設計訂單系統

背景:因爲StarBuzz咖啡連鎖店業務擴張,準備更新訂單系統,以合乎他們的飲料供應要求。學習

他們原來的類設計以下:測試

 

用戶在購買咖啡的時候,能夠能會要求在咖啡中加入各類調料,StarBuzz會根據用戶加入的不一樣調收取不一樣費用,新的訂單系統必須考慮到這些調料部分。this

1.1 第一次設計

 

以上的每個類的Cost()方法將會算出咖啡加上訂單的各類調料的價錢。雖然能夠知足需求,可是這樣會須要不少不少的類,並且也違反了OO的設計原則。編碼

1.2 第二次設計

不須要建立那麼多的類,只須要經過利用實例變量和繼承,就能夠追蹤調料。設計

設計以下:對象

 

這樣作,確實能夠暫時知足需求,可是還會存在一些潛在的隱患,以下:blog

l  調料價格變更會改變原有代碼繼承

l  新增調料,除了加上新增方法外,還須要改變超類中的Cost()方法

l  依賴繼承,子類將繼承一些對自身並不合適的方法

l  部分需求沒法知足:如雙倍摩卡咖啡

l  違反了開放—關閉原則

2.    引入裝飾者模式

2.1 開發----關閉原則

在上一節的第二次設計中咱們能夠看出這種設計方法明顯的違背了「開發—關閉」原則,那什麼是開閉原則呢?定義以下:

開發—關閉原則:類應該是對擴展開放,對修改關閉。

咱們的目標是容許類容易擴展,在不修改現有代碼的狀況下,就可搭配新的行爲,這樣的設計具備彈性能夠應對改變,能夠接受新的功能來應對改變的需求。遵循開開放—關閉原則,一般會引入新的抽象層次,增長代碼的複雜度,須要選擇在設計中最可能改變的地方,而後應用開發-關閉原則。

2.2 認識裝飾者模式,並以裝飾者構造飲料訂單

在1中咱們已經瞭解到經過繼承沒法徹底解決問題,這裏咱們以飲料爲主體,而後在運行時以調料來「裝飾」飲料。例如:若是客戶想要摩卡和奶泡深焙咖啡,以下:

l  拿一個深焙(DarkRoast)對象

l  以摩卡(Mocha)對象裝飾它

l  以奶泡(Whip)對象裝飾它

l  調用Cost()方法,並依賴委託(delegate)將調料的價錢加上去

說明:以DarkRoast對象開始,顧客想要摩卡(Mocha),因此創建一個Mocha對象,用它將DarkRoast對象包起來,顧客想要奶炮(Whip),因此須要創建一個Whip裝飾者,並用它將Mocha對象包起來。最後算錢,經過調用最外層的裝飾者Whip的Cost()方法就能夠辦到。其調用過程以下:

Whip.Cost()àMocha.Cost()àDarkRoast.Cost()(返回DarkRoast的價錢)àMocha.Cost()(Mocha在DarkRoast的結果上加上本身的價錢)àWhip.Cost()(Whip在Mocha的返回結果上加上本身的價錢)

基於以上的這種分析,咱們能夠得出如下的結論:

裝飾者和被裝飾對象有相同的超類型。

能夠用一個或者多個裝飾者包裝一個對象。

由於裝飾者和被裝飾者有相同的超類型,因此在任何須要原始對象的場合,能夠用裝飾過的對象代替它。

裝飾者能夠在所委託被裝飾者的行爲以前或者以後,加上本身的行爲,達到特定目的。

對象能夠在任什麼時候候被裝飾,因此能夠在運行時動態的用你喜歡的裝飾者來裝飾對象。

2.3 定義裝飾者模式

裝飾者模式:

動態的將責任附加到對象上,若要擴展功能,裝飾者提供了更有彈性的替代方案。

設計類圖以下:

 

 

依據這種裝飾者模式的類圖,咱們設計出StarBuzz的類圖,也讓它符合這種結構設計,類圖以下:

 

裝飾者和被裝飾者都繼承自Beverage類,也就是擁有共同的超類,這裏,咱們是利用繼承達到「類型匹配」,而不是利用繼承得到「行爲」。

裝飾者和組件組合時,就是在加入新的行爲,所得的新行爲並非繼承自超類,而是由組合對象得來的。

若是依賴繼承,那麼類的行爲只能在編譯時靜態決定,行爲不是來自超類就是子類覆蓋後的版本,每當須要新行爲時,還得修改現有代碼。若是利用組合,就能夠動態的實現新的裝飾者增長新的行爲。

3 用裝飾者模式實現咖啡店需求

根據在2.3中設計的咖啡店的類圖,下面就進行具體的編碼實現:

3.1 Beverage類(抽象組件)

/// Description: Beverage抽象類
    /// </summary>
    public abstract class Beverage
    {
        public string description = "Unknown Beverage";
        public abstract string GetDescription();
        public abstract double Cost();
    }

3.2Condiment(調料)基類(繼承自Beverage基類,抽象裝飾者)

/// Description:調料基類、派生類
    /// </summary>
    public abstract class CondimentDecorator:Beverage
    {
        //public  abstract string GetDescription();
    }

3.3飲料類(繼承Beverage基類,具體組件)

 public class Espresso:Beverage
    {
        public Espresso()
        {
            description = "Espresso";//設置飲料的表述,description繼承自Beverage類的實例變量
        }

        public override double Cost()
        {
            return 1.99;
        }

        public override string GetDescription()
        {
            return description;
        }
    }

    public class HouseBlend : Beverage
    {
        public HouseBlend()
        {
            description = "HouseBlend";
        }

        public override double Cost()
        {
            return 0.89;
        }

        public override string GetDescription()
        {
            return description;
        }
    } 

    public class DarkRoase : Beverage
    {
        public DarkRoase()
        {
            description = "DarkRoase";
        }

        public override double Cost()
        {
            return 1.11;
        }
        public override string GetDescription()
        {
            return description;
        }
    }

    public class Decat : Beverage
    {
        public Decat()
        {
            description = "Decat";
        }

        public override double Cost()
        {
            return 1.22;
        }

        public override string GetDescription()
        {
            return description;
        }
    }

3.4調料類(裝飾者)

public class Mocha : CondimentDecorator
    {
        Beverage beverage;
        public Mocha(Beverage beverage)
        {
            this.beverage = beverage;
        }

        public override string GetDescription()
        {
            return beverage.GetDescription() + ",Mocha";
        }
        public override double Cost()
        {
            return 0.2 + beverage.Cost();
        }
    }

    public class Soy : CondimentDecorator
    {
        Beverage beverage;
        public Soy(Beverage beverage)
        {
           this.beverage = beverage;
        }

        public override string GetDescription()
        {
            return beverage.GetDescription() + ",Soy";
        }

        public override double Cost()
        {
            return 0.3 + beverage.Cost();
        }
    }

    public class Whip : CondimentDecorator
    {
        Beverage beverage;
        public Whip(Beverage beverage)
        {
            this.beverage = beverage;
        }

        public override string GetDescription()
        {
            return beverage.GetDescription() + ",Whip";
        }

        public override double Cost()
        {
            return 0.3 + beverage.Cost();
        }
    }

3.5 測試

Beverage.Beverage beverage = new Beverage.Espresso();
            Console.WriteLine(beverage.GetDescription() + " $ " + beverage.Cost());
            Beverage.Beverage beverage1 = new Beverage.DarkRoase();
            beverage1 = new Beverage.Mocha(beverage1);
            beverage1 = new Beverage.Mocha(beverage1);
            beverage1 = new Beverage.Whip(beverage1);
            Console.WriteLine(beverage1.GetDescription() + " $ " + beverage1.Cost());

            Beverage.Beverage beverage2 = new Beverage.HouseBlend();
            beverage2 = new Beverage.Soy(beverage2);
            beverage2 = new Beverage.Mocha(beverage2);
            beverage2 = new Beverage.Whip(beverage2);
            Console.WriteLine(beverage2.GetDescription() + " $ " + beverage2.Cost());

結果以下圖:

 

4        總結

經過本章的學習,咱們能夠學到如下知識:

l  OO原則:

封裝變化

多用組合,少用繼承

針對接口編程,不針對實現編程

爲交互對象之間的鬆耦合設計而努力

對擴展開放,對修改關閉(本章節新學習的OO原則)

l  OO模式

裝飾者模式—動態地將責任附加到對象上,想要擴展功能,裝飾者提供有別於繼承的另外一種選擇。

l  要點概括

繼承和裝飾者均可以讓咱們擴展行爲,但繼承不是彈性設計的最佳方案。

裝飾者模式意味着一羣裝飾者類,裝飾者類反應了被裝飾組件的類型,能夠用多個裝飾者包裝對象。

裝飾者能夠在被裝飾者的行爲以前或者以後加上本身的行爲,甚至將被裝飾者的行爲取代,以到達特定目的。

裝飾者模式會致使設計中出現許多小對象,過分使用會使程序變得複雜。

相關文章
相關標籤/搜索