《Head.First设计模式》的学习笔记(4)--装饰者模式
意图:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
结构:
例子:
下面我们以星巴兹(Starbuzz)的订单系统为例加以说明。
需求分析:
1)、星巴兹的饮料(Beverage)种类繁多,主要有HouseBlend、DarkRoast、Decaf、Espresso。
2)、星巴兹的调料很多,主要有Steamed Milk、Soy、Mocha、Whip。
3)、星巴兹的饮料价格是根据饮料的基础价和所加入的调料的价格相加得到。
错误设计:
根据以上的简单分析,第一种类图设计出炉:
其中getDescription()用来描述饮料,cost()用来计算价格。
显而易见,这个类图设计的最大缺点就是类太多,系统难以维护。所以我们需要另外的解决方案,而且新方案必须避免“类爆炸”。此时我们想到了实例变量和继承。先从Beverage基类下手,加上实例变量代表是否加上调料(Steamed Milk、Soy、Mocha、Whip等),Beverage基类的cost()计算调料的价钱,而各种具体的饮料(HouseBlend、DarkRoast、Decaf、Espresso等)的cost()将把基础饮料的价钱和调料的价钱相加得到饮料的价钱。由此可以设计出第二种类图。
对这个类图设计的评价:如果需求不再变化,那么这个类图设计没有错;但是需求发生了变化,这个设计就会难以招架。经过进一步的分析,我们发现部分需求被我们遗漏了。
新增加的需求:
1)、调料的价格可能发生变化。
2)、调料的种类可能发生变化。
3)、饮料的种类可能增加,不只HouseBlend、DarkRoast、Decaf、Espresso四种。
4)、顾客可能在一种饮料里加双份的同种饮料。
显然,第二种类图设计难以满足新的需求,而且对新增加的饮料而言,有可能存在不适合的调料。例如,茶子类将继承hasWhip()(加奶泡)等方法。因此,我们应该对类图设计进行改进。此时我们想到了装饰者模式,那么如何应用装饰者模式呢?
以装饰者模式构造饮料订单:
2)、顾客想要Mocha,所以建立一个Mocha对象,并用它将DarkRoast对象包装起来。
3)、顾客也想要Whip,所以需要建立一个Whip装饰者,并用它将Mocha对象包起来。
4)、现在,该是为顾客算钱的时候了。通过最外圈装饰者(Whip)的cost()就可以办得到。Whip的cost()会先委托它装饰的对象(Mocha)计算出价钱,然后再加上Whip的价钱。
根据以上的分析,应用装饰者模式,我们可以打造一个全新的类图。
第三种类图设计(正确的类图):
部分代码为:
2 {
3 public DarkRoast()
4 {
5 description = "Dark Roast";
6 }
7 public override double Cost()
8 {
9 return 1.22;
10 }
11 }
12public class Mocha : CondimentDecorator
13 {
14 Beverage myBeverage;
15 public Mocha(Beverage paramBeverage)
16 {
17 this.myBeverage = paramBeverage;
18 }
19
20 public override string GetDescription()
21 {
22 return myBeverage.GetDescription() + ",Mocha";
23 }
24
25 public override double Cost()
26 {
27 return 0.5 + myBeverage.Cost();
28 }
29 }
30class StarbuzzCoffee
31 {
32 static void Main(string[] args)
33 {
34 Beverage myBeverage = new Espresso();
35 Console.WriteLine(myBeverage.GetDescription() + " $" + myBeverage.Cost());
36
37 Beverage myBeverage2 = new DarkRoast();
38 myBeverage2 = new Mocha(myBeverage2);
39 myBeverage2 = new Mocha(myBeverage2);
40 myBeverage2 = new Whip(myBeverage2);
41 Console.WriteLine(myBeverage2.GetDescription() + " $" + myBeverage2.Cost());
42
43 Console.ReadLine();
44 }
45 }
装饰模式的适用情况:
1)、需要扩展一个类的功能,或给一个类增加附加责任。
2)、需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
3)、需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。
使用装饰模式主要有以下的优点:
1)、装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
2)、通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
使用装饰模式主要有以下的缺点:
由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
参考文献:
《Head.First设计模式》
吕震宇 设计模式系列