装饰者模式(Decorator Pattern)

1. 概述

装饰者模式 动态的 将责任附加到对象上。

若要扩展功能,装饰者提供了比继承更有弹性的解决方案

2. 新的设计原则

类应当对扩展开放,对修改关闭

这乍看上去很矛盾,如何做到“既开放又关闭” 呢?

实际上,我们可以采用 组合委托 来达到扩展的目的;

避免因为扩展而需要修改代码

Bug 总是在修改、新增代码时引入的;
如果能够尽量减少对代码的反复更改,那么就可以更有效的减少和避免 Bug

装饰者模式就很好的体现了 “开放——关闭” 原则。

3. 原理

使用不同的 装饰者对象 来对 主体对象 进行装饰;

通过 委托 来进行组合工作。

这里有一个很重要的地方就是,为什么能实现上面图示的 包装方法委托

其使用到的技巧就是, 装饰者对象实际上也是主体对象,即它们有相同的超类。

如果不具备相同超类的话,最多只能做到一层包装,而无法做到动态的,多层包装。

注意,这里采用相同的超类,实际上只是为了做到 类型匹配,而装饰者并没有从超类中继承它的 行为

继承的原罪在于, 在运行时,行为需要改变! 如果行为从继承中得到,那么它在编译时就会被确定,也就是所谓的 与具体实现绑定

但是,如果行为不从继承中得到,那么继承反而成了优点,因为继承可以实现多态,为我们的动态扩展提供合适的条件

4. 特点

  1. 装饰者和被装饰对象有 相同的超类型
  2. 可以使用一个或者 多个 装饰者来包装对象
  3. 由于有相同的超类型,所以在需要被装饰对象的场合时,可以使用装饰过后的对象来替代
  4. 装饰者可以在所委托被装饰者的行为之前、之后,加上自己的行为,以达到特定的目的
  5. 对象可以在任何时候被装饰,可以在运行时,动态地、不限量地对对象进行装饰

5. UML 图解

可以看到,装饰者和主体对象有一个 共同的超类

同时, DecoratorComponent 都是 抽象类

对于实际的装饰者,他们都包含了一个 Component 实例,这就是被其装饰的对象,通过多态来进行方法委托。

图中的 wrappedObject 需要从外部获得,一般来说,是通过 构造函数 传入的。

6. 例子图解

7. 真实的装饰者模式——Java IO

Java IO 库中大量使用了装饰者模式,这也就是为什么会出现如下的代码:

1
2
InputStream in =
new BufferdInpuStream(new FileInputSteram());

可以看到 FileInputSteram 是被装饰的主体对象,而 BufferdInpuStream 是装饰对象。

8. 缺陷

装饰者对象的缺陷很明显,就是会 增加大量的小对象

同时,由于装饰者模式是通过 层层委托 来实现扩展的;

所以,当装饰者需要改变的时候,就需要将改变应用到 所有的装饰者

此时,当装饰者数量较多时,更改难度大。

0%