一、咖啡店的故事
这次我们借用HeadFirst中的咖啡店的故事来讨论一下装饰模式。咖啡店中有各种种类的咖啡和咖啡需要加的配料。有一家咖啡店为了提高效率打算开发一套咖啡订购系统,用户可以根据清单选择咖啡和咖啡所加的配料,系统可以自动的计算总价格。
第一种方案是这个样子的:
Beverge是一个抽象类,店内所有的饮料都必须继承自这个类。description用来描述这个是什么类型的饮料例如:Dark Roast。getDescription()方法返回这种饮料的描述,cost()返回这种饮料的描述,但是cost()方法是抽象的,具体的实现由子类决定,因为每中不同饮料价格都不相同。下面的四个类代表四种不同的饮料,他们都实现了父类的cost方法。
因为用户购买咖啡时还需要搭配一些调料,所以每种咖啡又分为各种不同的种类,于是系统就变成了这个样子。
这种方案造成的最大的问题就是类的数量众多,维护成本非常大。试想如果牛奶的价格上涨,那么每种添加牛奶的咖啡就都必须修改自己的价格,所以这种方案不符合“开-闭”原则。
第二种方案:
milk,soy,mocha,whip都是代表有没有这种调料的布尔值,下面的hasMilk()方法等是返回这个变量的值,setMilk()方法是设置这个变量的值。并且Beverage中的cost()方法也不再是抽象方法而是返回所加的各种调料的价格,子类会覆盖这个cost方法,子类的cost方法会返回总的价格,首先调用父类的cost方法得到调料的价格,然后再加上咖啡的价格,返回总的价格。
采用这种方法只需要5各类就能表示出这家咖啡店中所有的咖啡和配料组合。但是仍然存在问题,试想,如果我们如果增加了一种新的调料,那么Beverage类不就需要更改吗,如果客户想买双倍摩卡咖啡怎么办?如果以后开发出了一种新的饮料,比如说“茶”,对于这种饮料而言某些调料是不合适的,比如说(奶泡),但是它还是继承了所有的方法,包括不合适的。
第三种方案:装饰模式
我们把咖啡本身当做是主要的本体,而把奶泡等调料当做是咖啡的装饰,我们通过给本体添加不同的装饰来获得不同的结果。
这是咖啡的本体,我们以DarkRoast为例,这种咖啡是继承自Beverage的,它的cost()是用来返回咖啡的价格。
如果客户想要摩卡咖啡,就建立一个Mocha对象,并用他将DarkRoast对象包起来(装饰)。Mocha就是一个装饰者,他的类型和他所装饰的类型是一样的,都是Beverage。Mocha也有一个cost方法,它的cost()方法会首先调用所装饰的对象DarkRoast类的cost