引言
在山西面館吃雞蛋面的時候突然想起裝飾者這個模式,覺得面館這個場景跟書中的星巴茲咖啡的場景很像,邊吃邊思考裝飾者模式。這里也就依葫蘆畫瓢,換湯不換葯的用裝飾者模式來模擬一碗雞蛋面是怎么出來的吧。吃貨有吃貨的方式來理解......這里先將書中講到的例子放在前面,理論的東西,講的還是比較具體的,只是覺得咖啡的例子不是太好理解,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 設計模式