[Head First設計模式]山西面館中的設計模式——裝飾者模式


引言

在山西面館吃雞蛋面的時候突然想起裝飾者這個模式,覺得面館這個場景跟書中的星巴茲咖啡的場景很像,邊吃邊思考裝飾者模式。這里也就依葫蘆畫瓢,換湯不換葯的用裝飾者模式來模擬一碗雞蛋面是怎么出來的吧。吃貨有吃貨的方式來理解......這里先將書中講到的例子放在前面,理論的東西,講的還是比較具體的,只是覺得咖啡的例子不是太好理解,lz很土,幾乎沒喝過咖啡,不知道什么摩卡啊......,還是中國特色的例子更好理解。

為什么學設計模式?

答:覺得會設計模式的人,不僅僅是碼農,更像藝術家!

為什么現在學設計模式?

答:不求精通,但求認識,接觸過不少項目,有設計模式,而不認識,是我的損失,體會不到他的妙處,但不求它認識我。

裝飾者到底裝飾誰呢?

類,還是對象?

裝飾者模式定義

動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。(由定義可知,裝飾對象的)

實例分析

星巴茲咖啡訂單管理系統 管理、計算各種飲料的售價。

 若顧客根據個人喜好,添加不同的調料,那么系統就要有根據調料的不同來計算價格,按照原來的設計,必定會出現下面的情況。

 第一種方案設計:繼承

 有多少種口味的咖啡,你就得建多少種對應的類。煩不?

第二種方案設計:

思考

 當哪些因素改變時會影響這個設計?

調料價錢的改變會使我們更改現有代碼。

一旦出現新的調料,我們就需要加上新的方法,並改變超類中的cost()方法。

以后可能會開發出新飲料,對於這些飲料而言(冰茶),某些調料可能並不適合,但是在這個設計方式中,Tea子類仍將繼承那些不合適的方法,比如:hasWhip()。
如何顧客想要雙倍摩卡,怎么辦?

......

 設計原則(Open-Closed Principle)

 類應該對擴展開放,對修改關閉。

我們的目標是允許類容易擴展,在不修改現有代碼的情況下,就可搭配新的行為。如能實現這樣的目標,其好處在於:這樣的設計具有彈性,可以應對改變,可以接受新的功能來應對改變的需求。

如何讓設計的每個部分都遵循開放-關閉原則?

這通常是無法做到的。要讓OO設計同時具有開放性和關閉性,又不修改現有的代碼,需要花費許多時間和努力。一般來說,我們沒有足夠的精力把設計的每個部分都這么設計,這可能只是一種浪費。
遵循開放-關閉原則,通常會引入新的抽象層次,增加代碼的復雜度。你需要把注意力集中在設計中最有可能改變的地方,然后應用開放-關閉原則。

 裝飾者模式

星巴茲咖啡訂單管理系統——使用裝飾者模式

以飲料(Beverage)為主體,然后在運行時以調料(Condiment)來裝飾(decorate)飲料

比如,顧客想要摩卡和奶泡深焙咖啡,那么:
取出一個深焙咖啡(DarkRoast)對象
以摩卡(Mocha)對象裝飾它
以奶泡(Whip)對象裝飾它
調用cost方法,並依賴委托(delegrate)將調料的價格加上去

 步驟:

 

“裝飾者模式”——特點

裝飾者和被裝飾對象具有相同的超類型
可以用一個或多個裝飾者包裝一個對象
由於裝飾者和被裝飾對象有相同的超類型,所以在任何需要原始對象(被包裝)的場合,都可以用裝飾過的對象代替她
裝飾者可以在所委托被裝飾者的行為之前或之后,加上自己的行為,以達到特定的目的
對象可以在任何時候被裝飾,所以可以在運行時動態的、不限量的用需要的裝飾者來裝飾對象

 裝飾者模式類圖關系

星巴茲咖啡銷售系統裝飾者模式類圖關系

思考:為什么Decorate類擴展自Component類?

裝飾者和被裝飾者必須是一樣的類型,我們在此使用繼承達到“類型匹配”
類型匹配意味着裝飾者和被裝飾者具有相同的接口,從而裝飾者可以取代被裝飾者
新的行為並不是繼承自超類,而是由組合對象得到,即所有飲料和調料可以更有彈性的加以混合和匹配
我們可以在任何時候,實現新的裝飾者增加新的行為。如果依賴繼承,每當需要新行為時,必須修改代碼
Component類型可以使用抽象類,也可以使用接口

問題:如果有一張訂單:“雙倍摩卡豆漿奶泡拿鐵咖啡”,應該如何進行設計?

這里代碼的具體實現就不再寫了,網上這樣的代碼太多了?

山西面館中的“裝飾者模式”

 先上類圖:直觀形象

 代碼實現:

 1      /// <summary>
 2     ///抽象類 食物基類 其他類都繼承自該類
 3     /// </summary>
 4     public abstract class Food
 5     {
 6         protected string description = "未知的飯";
 7         public virtual string GetDescription()
 8         {
 9             return this.description;
10         }
11         public abstract double Cost();
12     }
1       /// <summary>
2     /// 所有配料的基類 繼承自Food類 保持類型一致
3     /// </summary>
4     public abstract class Ingredients : Food
5     {
6        
7     }

具體的配料:雞蛋,西紅柿類繼承配料類

 1 public class Egg : Ingredients
 2     {
 3         Food meal;
 4         public Egg(Food meal)
 5         {
 6             this.meal = meal;
 7 
 8         }
 9         public override double Cost()
10         {
11             return 3.0 + meal.Cost();
12         }
13 
14         public override string GetDescription()
15         {
16             return "雞蛋"+meal.GetDescription();
17         }
18     }
 1 public class Tomato : Ingredients
 2     {
 3         Food meal;
 4         public Tomato(Food meal)
 5         {
 6             this.meal = meal;
 7 
 8         }
 9         public override double Cost()
10         {
11             return 3.0 + meal.Cost();
12         }
13 
14         public override string GetDescription()
15         {
16             return "西紅柿" + meal.GetDescription();
17         }
18     }

所謂的component類(Noodle)繼承自Food類

 1   public class Noodle : Food
 2     {
 3         public Noodle()
 4         {
 5             description = "";
 6         }
 7         public override double Cost()
 8         {
 9             return 3.0;
10         }
11     }

控制台測試程序:

 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //創建被裝飾的對象 noodle
 6             Food f1 = new Noodle();
 7             //用雞蛋裝飾
 8             f1 = new Egg(f1);
 9             //用西紅柿裝飾
10             f1 = new Tomato(f1);
11             Console.WriteLine(f1.GetDescription() + "\t" + f1.Cost() + "");
12             Console.Read();
13         }
14     }

結果:

思考:裝飾者模式是否對修改封閉,對擴展開放呢?

那么,測試一下,加入現在我想要一份醬爆雞丁蓋澆飯,該怎么實現?

 1  public class Rice : Food
 2     {
 3         public Rice()
 4         {
 5             description = "蓋澆飯";
 6         }
 7         public override double Cost()
 8         {
 9             return 5.0;
10         }
11     }
 1  public class Chicken : Ingredients
 2     {
 3         Food meal;
 4         public Chicken(Food meal)
 5         {
 6             this.meal = meal;
 7         }
 8         public override string GetDescription()
 9         {
10             return "醬爆雞丁" + meal.GetDescription();
11         }
12         public override double Cost()
13         {
14             return 9.0 + meal.Cost();
15         }
16     }
 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //創建被裝飾的對象 noodle
 6             Food f1 = new Noodle();
 7             //用雞蛋裝飾
 8             f1 = new Egg(f1);
 9             //用西紅柿裝飾
10             f1 = new Tomato(f1);
11             Console.WriteLine(f1.GetDescription() + "\t" + f1.Cost() + "");
12             //創建被裝飾的對象 米飯
13             Food f2 = new Rice();
14             //用醬爆雞丁 裝飾(將配料合並了)
15             f2 = new Chicken(f2);
16             Console.WriteLine(f2.GetDescription() + "\t" + f2.Cost() + "");
17             Console.Read();
18         }
19     }


結果:

很容易擴展吧?只需要加兩個類繼承對應的類就可以了,原來的內部代碼不需要修改,就可以實現

總結

在我們的代碼中,應該允許行為遵循對擴展開放-對修改關閉的原則,這樣就可以無需修改現有的代碼就可以實現我們擴展的功能。

裝飾者模式意味着一群裝飾者類,這些類用來包裝具體組件。

裝飾者反映出被裝飾者的組件類型(具有相同的類型)

裝飾者可以在被裝飾者的行為前面或后面加上自己的行為,甚至將被裝飾者的行為整個取代,而達到特定的目的。

可以用無數個裝飾者包裝一個組件。

裝飾者一般對組件的客戶是透明的,除非客戶程序依賴於組件的具體類型。

裝飾者會導致設計中出現許多小對象,如果過度使用,會讓程序變得復雜。

(最近一直在看設計模式,上班下班的路上想,吃飯的時候也想,不求精通,只求在項目中遇到了,能認出他,不求他認識我)

參考書:

Head First 設計模式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM