前言:編程
本節將深度討論繼承濫用問題,將會學到使用對象組合的方式,在運行時裝飾類,在不修改任何底層代碼的狀況下,給對象賦予新的職責。ide
背景:因爲StarBuzz咖啡連鎖店業務擴張,準備更新訂單系統,以合乎他們的飲料供應要求。學習
他們原來的類設計以下:測試
用戶在購買咖啡的時候,能夠能會要求在咖啡中加入各類調料,StarBuzz會根據用戶加入的不一樣調收取不一樣費用,新的訂單系統必須考慮到這些調料部分。this
以上的每個類的Cost()方法將會算出咖啡加上訂單的各類調料的價錢。雖然能夠知足需求,可是這樣會須要不少不少的類,並且也違反了OO的設計原則。編碼
不須要建立那麼多的類,只須要經過利用實例變量和繼承,就能夠追蹤調料。設計
設計以下:對象
這樣作,確實能夠暫時知足需求,可是還會存在一些潛在的隱患,以下:blog
l 調料價格變更會改變原有代碼繼承
l 新增調料,除了加上新增方法外,還須要改變超類中的Cost()方法
l 依賴繼承,子類將繼承一些對自身並不合適的方法
l 部分需求沒法知足:如雙倍摩卡咖啡
l 違反了開放—關閉原則
在上一節的第二次設計中咱們能夠看出這種設計方法明顯的違背了「開發—關閉」原則,那什麼是開閉原則呢?定義以下:
開發—關閉原則:類應該是對擴展開放,對修改關閉。
咱們的目標是容許類容易擴展,在不修改現有代碼的狀況下,就可搭配新的行爲,這樣的設計具備彈性能夠應對改變,能夠接受新的功能來應對改變的需求。遵循開開放—關閉原則,一般會引入新的抽象層次,增長代碼的複雜度,須要選擇在設計中最可能改變的地方,而後應用開發-關閉原則。
在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的返回結果上加上本身的價錢)
基於以上的這種分析,咱們能夠得出如下的結論:
裝飾者和被裝飾對象有相同的超類型。
能夠用一個或者多個裝飾者包裝一個對象。
由於裝飾者和被裝飾者有相同的超類型,因此在任何須要原始對象的場合,能夠用裝飾過的對象代替它。
裝飾者能夠在所委託被裝飾者的行爲以前或者以後,加上本身的行爲,達到特定目的。
對象能夠在任什麼時候候被裝飾,因此能夠在運行時動態的用你喜歡的裝飾者來裝飾對象。
裝飾者模式:
動態的將責任附加到對象上,若要擴展功能,裝飾者提供了更有彈性的替代方案。
設計類圖以下:
依據這種裝飾者模式的類圖,咱們設計出StarBuzz的類圖,也讓它符合這種結構設計,類圖以下:
裝飾者和被裝飾者都繼承自Beverage類,也就是擁有共同的超類,這裏,咱們是利用繼承達到「類型匹配」,而不是利用繼承得到「行爲」。
裝飾者和組件組合時,就是在加入新的行爲,所得的新行爲並非繼承自超類,而是由組合對象得來的。
若是依賴繼承,那麼類的行爲只能在編譯時靜態決定,行爲不是來自超類就是子類覆蓋後的版本,每當須要新行爲時,還得修改現有代碼。若是利用組合,就能夠動態的實現新的裝飾者增長新的行爲。
根據在2.3中設計的咖啡店的類圖,下面就進行具體的編碼實現:
/// Description: Beverage抽象類 /// </summary> public abstract class Beverage { public string description = "Unknown Beverage"; public abstract string GetDescription(); public abstract double Cost(); }
/// Description:調料基類、派生類 /// </summary> public abstract class CondimentDecorator:Beverage { //public abstract string GetDescription(); }
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; } }
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(); } }
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());
結果以下圖:
經過本章的學習,咱們能夠學到如下知識:
l OO原則:
封裝變化
多用組合,少用繼承
針對接口編程,不針對實現編程
爲交互對象之間的鬆耦合設計而努力
對擴展開放,對修改關閉(本章節新學習的OO原則)
l OO模式
裝飾者模式—動態地將責任附加到對象上,想要擴展功能,裝飾者提供有別於繼承的另外一種選擇。
l 要點概括
繼承和裝飾者均可以讓咱們擴展行爲,但繼承不是彈性設計的最佳方案。
裝飾者模式意味着一羣裝飾者類,裝飾者類反應了被裝飾組件的類型,能夠用多個裝飾者包裝對象。
裝飾者能夠在被裝飾者的行爲以前或者以後加上本身的行爲,甚至將被裝飾者的行爲取代,以到達特定目的。
裝飾者模式會致使設計中出現許多小對象,過分使用會使程序變得複雜。