Head First設計模式——裝飾者模式
前言:對於設計模式我們有時候在想是否有必要,因為實際開發中我們沒有那麼多閒工夫去套用這麼多設計模式,也沒有必要為了模式而模式。
通常這些模式會引入新的抽象層,增加程式碼的複雜度,但是當我們掌握了這些設計模式,
在系統中比較棘手或者需要以後修改擴充套件的地方採用了合適的設計模式會讓我們的系統易於擴充套件維護甚至工作變得輕鬆很多。
對於這一點我深有體會,有時候設計的比較好的功能模組在後來客戶改變需求的時候變得很容易且方便新增修改。
但是如果比較糟糕偷懶的方式會讓我們對自己的程式碼修改變得害怕,害怕客戶提需求,害怕去動自己的程式碼。
所以對於框架或者這些設計模式我們可以不用過度使用,但是要用的時候能有儲備或者腦袋裡面會閃現出對應的解決方案。
這樣不光會提升我們的編碼興趣對我們開發的產品也更有信心。
本篇就聚焦Head First設計模式中的裝飾者模式進行學習和總結。
咖啡店結算案例
咖啡店賣各種咖啡,根據調料不一樣就會有不同價格的咖啡飲品可選,那麼對應的咖啡飲品價格也不一樣。
咖啡是需要按照基礎價格+調料組合計算價格的,那按照繼承的方式我們抽象一個飲料基類Beverage,Beverage擁有Cost抽象方法用於計算價格。
其他咖啡飲品繼承Beverage實現Cost方法根據自己的調料計算價格。
public abstract class Beverage { public abstract void Description(); public abstract float Cost(); } public class DarkRoast : Beverage { public override void Description() { //深焙咖啡 } public override float Cost() { //各種調料價格計算 } }
對於繼承的方式,其他子類都要去實現一遍價格計算無法複用,如果子類比較多那麼繼承的子類會“爆炸”,新增加子類對於我們來說就是一項重複工作。
如果說某一種調料的價格變化我們還得去每個子類裡面改變價格。
那我們針對變化的調料部分是不是讓他們按照實際需求組合,在子類中確定自己加哪些調料就行了,似乎這種方式會減少重寫和維護難度。
按照這個思路將Beverage改造,將是否有調料定義成變數,然後Cost方法不再抽象而是提供實現。
public abstract class Beverage { //牛奶 public bool Milk { get; set; } //糖 public bool Suger { get; set; } //摩卡 public bool Mocha { get; set; } public abstract void Description(); public virtual float Cost() { float price = 0; if (Milk) { price += 1; } if (Suger) { price += 2; } if (Mocha) { price += 3; } return price; } } public class DarkRoast : Beverage { public override void Description() { Console.WriteLine("深焙咖啡"); } public override float Cost() { Milk = true; Suger = true; return 1.1f+base.Cost(); } }
這種方式比之前的繼承的確好了許多,不過還是有如下幾個問題:
1、調料價格改變會使我們改變現有程式碼。
2、需要新增新的調料,我們就需要新增新的方法並改變Beverage基類的Cost方法。
3、某些調料可能並不適合其他飲品,例如茶還要繼承這些不屬於它的調料。
4、如果顧客需要雙份糖,Cost方法就會失效。
接下來我們就用"裝飾者模式"來更好的設計該案例。
認識裝飾者模式
這裡有一個設計模式很重要的設計原則:類應該對擴充套件開放,對修改關閉(開閉原則)
裝飾模式同樣遵循開閉原則。
對於咖啡店來說,主體是飲料不變,變化的是調料。我們以飲料為主體,其他調料來“裝飾”飲料。比如顧客想要加了Mocha(摩卡)的 DarkRoast(深焙咖啡),我們要做的是:
1、製造一個DarkRoast物件
2、以Mocha(摩卡)物件裝飾它
3、呼叫Cost()方法,並依賴委託(非C# delegate,只是概念)將調料的價錢加上去
利用裝飾者模式
首先我們修改主體飲料Beverage基類,GetDescription 返回描述,Cost由子類自己實現定價。
public abstract class Beverage { string description = "Unkonwn Beverage"; public virtual string GetDescription() { return description; } public abstract float Cost(); }
深焙咖啡類
public class DarkRoast : Beverage { public DarkRoast() { description = "深焙咖啡"; } public override float Cost() { return 1.1f; } }
裝飾類我們定義一個抽象基類Condiment(調料)並繼承Beverage。
為什麼要定義一個抽象裝飾基類,因為裝飾類可能需要抽象出其他方法,而且因為我們用裝飾類去裝飾了被裝飾者後 我們本身也應該變成可已讓別人裝飾的類。所以裝飾類繼承CondimentDecorator,CondimentDecorator繼承的是Beverage。此例暫未加入裝飾類擁有的屬性和方法
public abstract class CondimentDecorator:Beverage { }
我們再實現兩個裝飾類:Mik和Suger,用變數記錄被裝飾者,在建構函式裡面設定被裝飾者。
public class Milk : CondimentDecorator { //用變數記錄被裝飾者 Beverage beverage; public Milk(Beverage beverage) { this.beverage = beverage; } public override string GetDescription() { return beverage.GetDescription() + "+Milk"; } public override float Cost() { return beverage.Cost() + 1; } } public class Suger : CondimentDecorator { //用變數記錄被裝飾者 Beverage beverage; public Suger(Beverage beverage) { this.beverage = beverage; } public override string GetDescription() { return beverage.GetDescription() + "+Suger"; } public override float Cost() { return beverage.Cost() + 2; } }
編寫測試結果:
總結
裝飾者模式說明:動態地將責任附加到物件上。若要擴充套件功能,裝飾者提供了比繼承更有彈性的替代方案。
畫出本例中裝飾者模式類圖以便理解和記憶
&n