装饰者模式(Decorator Pattern)
1. 概述
装饰者模式 动态的 将责任附加到对象上。
若要扩展功能,装饰者提供了比继承更有弹性的解决方案
2. 新的设计原则
类应当对扩展开放,对修改关闭
这乍看上去很矛盾,如何做到“既开放又关闭” 呢?
实际上,我们可以采用 组合 和 委托 来达到扩展的目的;
而 避免因为扩展而需要修改代码
Bug 总是在修改、新增代码时引入的;
如果能够尽量减少对代码的反复更改,那么就可以更有效的减少和避免 Bug
装饰者模式就很好的体现了 “开放——关闭” 原则。
3. 原理
使用不同的 装饰者对象 来对 主体对象 进行装饰;
通过 委托 来进行组合工作。
这里有一个很重要的地方就是,为什么能实现上面图示的 包装 和 方法委托
其使用到的技巧就是, 装饰者对象实际上也是主体对象,即它们有相同的超类。
如果不具备相同超类的话,最多只能做到一层包装,而无法做到动态的,多层包装。
注意,这里采用相同的超类,实际上只是为了做到 类型匹配,而装饰者并没有从超类中继承它的 行为。
继承的原罪在于, 在运行时,行为需要改变! 如果行为从继承中得到,那么它在编译时就会被确定,也就是所谓的 与具体实现绑定。
但是,如果行为不从继承中得到,那么继承反而成了优点,因为继承可以实现多态,为我们的动态扩展提供合适的条件
4. 特点
- 装饰者和被装饰对象有 相同的超类型
- 可以使用一个或者 多个 装饰者来包装对象
- 由于有相同的超类型,所以在需要被装饰对象的场合时,可以使用装饰过后的对象来替代
- 装饰者可以在所委托被装饰者的行为之前、之后,加上自己的行为,以达到特定的目的
- 对象可以在任何时候被装饰,可以在运行时,动态地、不限量地对对象进行装饰
5. UML 图解
可以看到,装饰者和主体对象有一个 共同的超类
同时, Decorator
和 Component
都是 抽象类
对于实际的装饰者,他们都包含了一个 Component
实例,这就是被其装饰的对象,通过多态来进行方法委托。
图中的 wrappedObject
需要从外部获得,一般来说,是通过 构造函数 传入的。
6. 例子图解
7. 真实的装饰者模式——Java IO
Java IO 库中大量使用了装饰者模式,这也就是为什么会出现如下的代码:
InputStream in =
new BufferdInpuStream(new FileInputSteram());
可以看到 FileInputSteram
是被装饰的主体对象,而 BufferdInpuStream
是装饰对象。
8. 缺陷
装饰者对象的缺陷很明显,就是会 增加大量的小对象。
同时,由于装饰者模式是通过 层层委托 来实现扩展的;
所以,当装饰者需要改变的时候,就需要将改变应用到 所有的装饰者;
此时,当装饰者数量较多时,更改难度大。