decorator

欢迎来到星巴兹咖啡

星巴兹(Starbuzz)是以扩张速度最快而闻名的咖啡连锁店之一,因为扩张速度实在太快,他们准备更新订单系统,以合乎他们的饮料供应要求。他们原来的设计是这样的……

 

decorator

购买咖啡时,也可以在其中加入各种饮料。例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力)或覆盖奶泡。星巴兹会根据所加入的饮料收取不同的费用。

QQ图片20160419072120

这简直是“类”爆炸,干嘛设计这么多类,可以利用实例变量和继承追踪这些调料呀!先从Beverage基类下手,加上实例变量代表是否加上饮料(牛奶、豆浆、摩卡、奶泡……)

decorator

Beverage类中的cost()不再是个抽象方法,提供了它的实现,计算要加入各种饮料的调料价钱。子类覆盖cost()并扩展其功能,把指定的饮料类型的价钱加进来。然而,这种设计存在着问题:若有其它类型的饮料加进来,需要在Beverage类中假如成员变量,并生成setteer/getter方法,需要再写个子类继承它,这违背了重要的设计原则:类应该对扩展开放,对修改关闭。也就是说子类可以扩展父类Beverage,但不能修改其内部结构。

认识装饰者模式

好了,我们已经了解利用继承无法完全解决问题,在星巴兹遇到的问题有:类数量爆炸、设计死板、以及基类加入的新功能并不适用于所有的子类。所以,这里需要采用不一样的做法:一饮料为主题,然后运行时以调料来“装饰”(decorate)饮料。比如,顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:

1、拿一个深焙咖啡(DarkRoast)对象

2、以摩卡(Mocha)对象装饰它

3、以奶泡(Whip)对象装饰它

4、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。

以装饰者构造饮料订单

1、以DarkRoast对象开始

decorator

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)计算出价钱,然后加上奶泡的价钱。

QQ图片20160421062930

通过以上可以发现:

1、装饰者和被装饰对象有相同的超类型;

2、可以用一个或多个装饰者包装一个对象;

3、既然装饰者和被装饰者有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它;

4、装饰者可以在所委托被装饰者的行为之前/之后,加上自己的行为,以达到特定的目的;

5、对象可以在任何时候被装饰,所以可以在运行时动态地,不限量的用你喜欢的装饰者来装饰对象。

定义装饰者模式

装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的解决方案。

QQ图片20160421064823

Java I/O中的装饰者模式

Java I/O中FileInputStream是被装饰的“组件”,I/O程序库包含了FileInputStream、StringBufferInputStream、FileInputStream、ByteArrayInputStream……这些类都提供了最基本的字节读取功能。

BufferedInputStream是一个具体的“装饰者”,他加入了两种行为:利用缓冲输入来改进性能;用一个readLine()方法(用来一次读取一行文本输入数据)来增强接口。

LineNumberInputStream也是一个具体的“装饰者”,他加上了计算行数的能力。

BufferedInputStream及LineNumberInputStream都扩展自FilterInputStream,而FilterInputStream是一个抽象的装饰类。

Java I/O引出装饰者模式的一个缺点:利用装饰者模式,常常造成设计中有大量的小类,数量很多,会造成使用此API的困扰。

posted @ 2016-04-20 06:31  devotion987  阅读(239)  评论(0编辑  收藏  举报