裝飾者模式 Decorator
阿新 • • 發佈:2019-01-03
專案:咖啡計費系統
背景:現有系統中有一個抽象類Beverage,有2個抽象方法GetDescription和Cost。
1 namespace DecoratorPattern 2 { 3 /// <summary> 4 /// 飲料抽象類 5 /// </summary> 6 public abstract class Beverage 7 { 8 protected string description = "飲料"; 9 protected float price = 0f; 10 publicabstract string GetDescription(); 11 12 public abstract float Cost(); 13 } 14 }
需求:目前有綜合咖啡、深焙咖啡、濃縮咖啡,調料有牛奶、摩卡、豆漿、奶泡。未來可能增加新的咖啡種類和調料,當顧客點咖啡時,要求能夠獲得咖啡的描述和價格。
設計方案1:設計綜合咖啡、深焙咖啡、濃縮咖啡4個子類,繼承Beverage。再用這4個子類分別派生4個子類,帶有牛奶的綜合咖啡,帶有摩卡的綜合咖啡,帶有豆漿的綜合咖啡...
分析:缺點時顯而易見的,這樣做導致“類爆炸”,一共需要3*4=12個子類。
設計方案2:把調料作為咖啡的屬性設定在Beverage裡,並增加方法HasMilk(),SetMilk()等類似的方法。
分析:缺點這樣做無疑時從一個災難跳進另一個災難中。我們在開發中應當儘量避免修改已有程式碼,遵循“開閉原則”。而且當增加新的飲料時,又要修改基類。
另一個災難是,子類在計算價格時,需要大量的分支結構來判斷是否包含某種調料,以計算咖啡的價格,我們總是儘量的避免複雜的分支結構,這使得維護變得非常困難。
還有針對實現程式設計帶來的問題,不能夠動態的新增職責。
裝飾者模式 Decorator 閃亮登場:
裝飾者模式動態的將職責附加到物件上。若要擴充套件共呢個,裝飾者提供了比繼承更有彈性的替代方案。
1. 4個基類繼承Beverage
1 namespace DecoratorPattern 2 { 3 /// <summary> 4 /// 綜合咖啡 5 /// </summary> 6 public class HouseBlend:Beverage 7 { 8 public HouseBlend(float price) 9 { 10 this.price = price; 11 this.description = "綜合咖啡"; 12 } 13 14 public override float Cost() 15 { 16 return price; 17 } 18 public override string GetDescription() 19 { 20 return this.description; 21 } 22 } 23 }
1 namespace DecoratorPattern 2 { 3 /// <summary> 4 /// 深焙咖啡 5 /// </summary> 6 public class DarkRoast:Beverage 7 { 8 public DarkRoast(float price) 9 { 10 this.price = price; 11 this.description = "深焙咖啡"; 12 } 13 public override string GetDescription() 14 { 15 return this.description; 16 } 17 public override float Cost() 18 { 19 return price; 20 } 21 } 22 }
1 namespace DecoratorPattern 2 { 3 /// <summary> 4 /// 濃縮咖啡 5 /// </summary> 6 public class Espresso:Beverage 7 { 8 public Espresso(float price) 9 { 10 this.price = price; 11 this.description = "濃縮咖啡"; 12 } 13 public override float Cost() 14 { 15 return this.price; 16 } 17 public override string GetDescription() 18 { 19 return this.description; 20 } 21 } 22 }
裝飾者繼承Beverage,注意這裡繼承的目的並不是為了獲得基類的功能,而是為了型別匹配,達到多型的目的,獲得功能由組合來實現。因此每一個裝飾者都需要維護一個Beverage引用。
1 namespace DecoratorPattern 2 { 3 /// <summary> 4 /// 摩卡裝飾者,為了能夠取代Beverage,所以CondimentDecorator繼承自Beverage,目的並非獲得Beverage 5 /// 而是為了型別匹配 6 /// </summary> 7 public class Mocha : Beverage 8 { 9 //持有抽象類飲料的引用,達到執行時新增職責的目的 10 Beverage beverage; 11 //包裝Beverage 12 public Mocha(Beverage b, float price) 13 { 14 beverage = b; 15 this.price = price; 16 } 17 public override float Cost() 18 { 19 return beverage.Cost() + price; 20 } 21 22 public override string GetDescription() 23 { 24 return beverage.GetDescription() + ", 摩卡"; 25 } 26 } 27 }
1 namespace DecoratorPattern 2 { 3 /// <summary> 4 /// 奶泡裝飾者 5 /// </summary> 6 public class Whip : Beverage 7 { 8 private Beverage beverage; 9 public Whip(Beverage b, float price) 10 { 11 beverage = b; 12 this.price = price; 13 } 14 public override float Cost() 15 { 16 return beverage.Cost() + price; 17 } 18 public override string GetDescription() 19 { 20 return (beverage.GetDescription() + " ,奶泡"); 21 } 22 } 23 }
1 namespace DecoratorPattern 2 { 3 /// <summary> 4 /// 豆漿裝飾者 5 /// </summary> 6 public class Soy : Beverage 7 { 8 private Beverage beverage; 9 public Soy(Beverage b, float price) 10 { 11 this.price = price; 12 beverage = b; 13 } 14 public override float Cost() 15 { 16 return beverage.Cost() + price; 17 } 18 19 public override string GetDescription() 20 { 21 return( beverage.GetDescription() + ", 豆漿"); 22 } 23 } 24 }
客戶端類:CoffeeShop.cs
1 using System; 2 3 namespace DecoratorPattern 4 { 5 class CoffeeShop 6 { 7 static void Main(string[] args) 8 { 9 //來一杯濃縮咖啡,不要調料 10 Beverage beverage = new Espresso(1.99f); 11 Console.WriteLine(beverage.GetDescription() + "$" + beverage.Cost()); 12 13 //來一杯摩卡奶泡深焙咖啡 14 Beverage beverage2 = new DarkRoast(0.99f); 15 beverage2 = new Whip(beverage2, 0.1f); //用奶泡裝飾深焙咖啡 16 beverage2 = new Mocha(beverage2, 0.2f); //再用摩卡裝飾 17 Console.WriteLine(beverage2.GetDescription() + "$" + beverage2.Cost()); 18 19 20 Console.ReadKey(); 21 } 22 23 } 24 }
執行結果:
參考資料《Head First 設計模式》