欢迎来到星巴兹咖啡
星巴兹(Starbuzz)是以扩张速度最快而闻名的咖啡连锁店之一,因为扩张速度实在太快,他们准备更新订单系统,以合乎他们的饮料供应要求。他们原来的设计是这样的……
购买咖啡时,也可以在其中加入各种饮料。例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力)或覆盖奶泡。星巴兹会根据所加入的饮料收取不同的费用。
这简直是“类”爆炸,干嘛设计这么多类,可以利用实例变量和继承追踪这些调料呀!先从Beverage基类下手,加上实例变量代表是否加上饮料(牛奶、豆浆、摩卡、奶泡……)
Beverage类中的cost()不再是个抽象方法,提供了它的实现,计算要加入各种饮料的调料价钱。子类覆盖cost()并扩展其功能,把指定的饮料类型的价钱加进来。然而,这种设计存在着问题:若有其它类型的饮料加进来,需要在Beverage类中假如成员变量,并生成setteer/getter方法,需要再写个子类继承它,这违背了重要的设计原则:类应该对扩展开放,对修改关闭。也就是说子类可以扩展父类Beverage,但不能修改其内部结构。
认识装饰者模式
好了,我们已经了解利用继承无法完全解决问题,在星巴兹遇到的问题有:类数量爆炸、设计死板、以及基类加入的新功能并不适用于所有的子类。所以,这里需要采用不一样的做法:一饮料为主题,然后运行时以调料来“装饰”(decorate)饮料。比如,顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:
1、拿一个深焙咖啡(DarkRoast)对象
2、以摩卡(Mocha)对象装饰它
3、以奶泡(Whip)对象装饰它
4、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。
以装饰者构造饮料订单
1、以DarkRoast对象开始
2、顾客想要摩卡(Mocha),所以建立一个Mocha对象,并用它将DarkRoast对象包装(wrap)起来。Mocha对象是个装饰者,它的类型“反映”了它所装饰的对象(本例中,就是Beverage)。所谓的“反映”,指的是两者类型一致。所以Mocha也有一个cost()方法,通过多态,可以把Mocha所包裹的任何Beverage当成是Beverage(因为Mocha是Beverage的子类型)。
3、顾客也想要奶泡(Whip),所以建立一个Whip装饰者,并用它将Mocha对象包装起来。别忘了,DarkRoast继承自Beverage,且有一个cost()方法,用来计算饮料价钱。
4、现在,是顾客算钱的时候了,通过最外圈装饰者Whip的cost()就可以办到。Whip的cost()会先委托它装饰的对象(Mocha)计算出价钱,然后加上奶泡的价钱。
通过以上可以发现:
1、装饰者和被装饰对象有相同的超类型;
2、可以用一个或多个装饰者包装一个对象;
3、既然装饰者和被装饰者有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它;
4、装饰者可以在所委托被装饰者的行为之前/之后,加上自己的行为,以达到特定的目的;
5、对象可以在任何时候被装饰,所以可以在运行时动态地,不限量的用你喜欢的装饰者来装饰对象。
定义装饰者模式
装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的解决方案。
Java I/O中的装饰者模式
Java I/O中FileInputStream是被装饰的“组件”,I/O程序库包含了FileInputStream、StringBufferInputStream、FileInputStream、ByteArrayInputStream……这些类都提供了最基本的字节读取功能。
BufferedInputStream是一个具体的“装饰者”,他加入了两种行为:利用缓冲输入来改进性能;用一个readLine()方法(用来一次读取一行文本输入数据)来增强接口。
LineNumberInputStream也是一个具体的“装饰者”,他加上了计算行数的能力。
BufferedInputStream及LineNumberInputStream都扩展自FilterInputStream,而FilterInputStream是一个抽象的装饰类。
Java I/O引出装饰者模式的一个缺点:利用装饰者模式,常常造成设计中有大量的小类,数量很多,会造成使用此API的困扰。