装饰者模式 Decorator
项目:咖啡计费系统
背景:现有系统中有一个抽象类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 public abstract 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 设计模式》