本文为看《Head First设计模式》书后整理的技术分享第三篇。
装饰者模式
这个设计模式可以学到如何使用对象组合的方式,做到在运行时装饰类。
一旦你熟悉了装饰的技巧,就能在不修改任何底层代码的情况下,给你的(或别人的)对象赋予新的职责。
设计原则
- 类应该对扩展开放,对修改关闭。(开放-关闭原则)
举例
星巴兹是以扩张速度最快而闻名的咖啡连锁店。因为扩张速度实在太快了,他们准备更新订单系统,以满足他们的饮料供应要求。
购买咖啡时,也可以要求在其中加入各种调料,例如:牛奶、豆浆、摩卡或奶盖。星巴兹会根据所加入的调料收取不同的费用。
所以订单系统必须要考虑到这些调料部分。
这是他们的第一个尝试:
很明显,这简直是为自己制造了一个维护噩梦,如果牛奶涨价,怎么办?新增一种焦糖调味,怎么办?
所以他们做了第二个尝试。
当哪些需求或因素改变时会影响这个设计?
- 调料价格的改变会使我们更改现有代码。
- 一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。
- 以后可能会开发出新饮料,对这些饮料而言(比如冰茶),某些调料可能并不适合,但是在这个设计中,Tea子类仍将继承那些不合适的方法,例如:hasWhip(加奶泡)。
- 万一顾客想要双倍摩卡的咖啡,怎么办?
我们已经了解到利用组合和委托可以在运行时具有继承行为的效果。
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为,然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态进行扩展。
所以我们可以利用此技巧把多个新职责,甚至是设计超类时还没想到的职责加在对象上,而且,可以不用修改原来的代码。
🌟第五个设计原则:开放—关闭原则:类应该对扩展开放,对修改关闭。
认识装饰者模式
我们已经了解到利用继承无法完全解决问题,在星巴兹遇到的问题有:类数量爆炸、设计死板、以及基类加入的新功能并不适用于所有的子类。
所以在这里我们要以饮料为主体,然后在运行时以调料来“装饰”饮料。那么。要做的是:
1:拿一个深焙咖啡(DarkRoast)对象。
2:以摩卡(Mocha)对象装饰它。
3:以奶泡(Whip)对象装饰它。
4:调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。
但是如何“装饰”一个对象,而“委托”又要如何与此搭配使用?(提示:把装饰者对象当成“包装者”)
以装饰者构造饮料订单
- 以DarkRoast对象开始。DarkRoast继承自Beverage,且有一个用来计算饮料价钱的cost()方法。
- 顾客想要摩卡(Mocha),所以建立一个Mocha对象,并用它将DarkRoast对象包(wrap)起来。
- 顾客也想要奶泡(Whip),所以需要建立一个Whip装饰者,并用它将Mocha对象包装起来。别忘了,DarkRoast继承自Beverage,并有一个cost()方法,用来计算饮料价钱。所以,被Mocha和Whip包起来的DarkRoast对象仍然可以具有DarkRoast的一切行为,包括调用它的cost()方法。
- 现在,该是顾客算钱的时候了,通过调用最外圈装饰者(Whip)的cost()就可以办得到。Whip的cost()会先委托它装饰的对象(也就是Mocha)计算出价钱,然后再加上奶泡的价钱。
装饰我们的饮料
具体实现代码
先从Beverage类下手:
1 | abstract class Beverage{ |
让我们也来实现Condiment(调料)抽象类,也就是装饰者类:
1 | //首先必须让CondimentDecorator能够取代Beverage,所以将CondimentDecorator扩展自Beverage |
写饮料的代码
现在,已经有了基类,让我们开始实现一些饮料吧,先从浓缩咖啡(Espresso)开始,别忘了,我们需要为具体的饮料设置描述,而且还必须实现cost()方法。
1 | //首先,让Espresso扩展自Beverage类,因为Espresso是一种饮料。 |
写调料代码
如果你回头看看装饰者模式的类图,将发现我们已经完成了抽象组件(Beverage),有了具体组件(HouseBlend),也有了抽象装饰者(CondimentDecorator)。现在, 我们就来实现具体装饰者,先从摩卡下手:
1 | //摩卡是一个装饰者,所以让它扩展自CondimentDecorator。别忘了,CondimentDecorator扩展自Beverage |
这里用来下订单的一些测试代码:
1 | class StarbuzzCoffee{ |
装饰者模式的特点:
- 装饰者和被装饰对象有相同的超类型。
- 你可以用一个或多个装饰者包装一个对象。
- 既然装饰者和被装饰者对象有相同的超类型,所有在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
- 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。(关键点)
- 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
总结定义
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
类图框架:
实际项目中的应用
真实世界的装饰者:Java I/O
java.io包内的类太多,如果你已经知道装饰者模式,这些I/O相关的类对你来说应该更有意义,因为其中许多类都是装饰者。下面是一个典型的对象集合:
装饰java.io类
编写自己的Java I/O装饰者
编写一个装饰者,把输入流内的所有大写字符转换成小写.
1 | class LowerCaseInputStream extends FilterInputStream{ |
装饰者有能力为设计注入弹性,但是也有一些“缺点”:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程序员的困扰。
但是,现在你已经了解了装饰者的工作原理,以后当使用别人的大量装饰的API时,就可以很容易地辨别出他们的装饰者类是如何组织的,以便用包装方式取得想要的行为。
拿Java I/O库来说,在人们第一次接触的时候无法轻易地理解,但是认识到这些类都是用来包装InputStream的,一切就简单多了。