设计原则
类应该对扩展开放,对修改关闭。
作用
动态地给一个对象添加一些额外的职责。就增加功能而言,装饰者模式相比生成子类更为灵活,因为生成子类,类的行为只能在编译时静态决定。换句话说,行为不是来自父类就是子类覆盖或者添加后的版本。反之,利用装饰者模式可以把装饰者嵌套使用,可以在运行时实现新的装饰者增加新的行为。如果依赖继承,每当我们需要新行为时还需要修改现有的代码,因为使用继承,类的行为只能在编译时静态决定。
实现要求
- 接口的一致性。因为装饰者必须必须能取代被装饰者,所以装饰者与被装饰者必须是同一类型,也就是说二者有共同的父类或者实现了共同的接口。 "类型匹配"可以通过抽象类(或者普通基类如IO的实现)、接口来实现。
- 利用组合和委托将被装饰者(组件)添加到装饰者当中。
- 省略不必要时的抽象的Decorator类。当你仅需要添加一个职责时,没有必要定义抽象Decorator类,你尝尝需要处理现存的类层次结构而不是设计一个新系统,这 时你可以把Decorator向Component转发请求的职责合并到ConcreteDecorator中。
- 保持接口/父类的简单性。组件和装饰必须有一个公共的Component父类。因此保持这个类的简单性是很重要的;即,它应集中于定义接口而不是存储数据。对数据表示的定义应延迟到子类中,否则Component类会变得过于复杂和庞大,因而难以大量使用。赋予赋予Component太多的功能也使得,具体的子类有一些它们并不需要的功能的可能性大大增加
装饰者模式与策略模式
改变对象外壳与改变对象内核。
- 我们可以将Decorator看作一个对象的外壳,它可以改变这个对象的行为。
- 另外一种方法是改变对象的内核。例如,Strategy模式就是一个用于改变内核的很好的模式。Component类原本就很庞大时,使用Decorator模式代价太高,Strategy模式相对更好一些。Strategy模式中,组件将它的一些行为转发给一个独立的策略对象,我们可以替换strategy对象,从而改变或扩充组件的功能。例如我们可以将组件绘制边界的功能延迟到一个独立的Border对象中,这样就可以支持不同的边界风格。这个Border对象是一个Strategy对象,它封装了边界绘制策略。我们可以将策略的数目从一个扩充为任意多个,这样产生的效果与对装饰进行递归嵌套是一样的。
装饰者模式的缺点
- 可能会造成大量的小类
- 客户代码中可能依赖某种特定类型(比如某个实现了"接口"的具体类中的非接口方法),如果导入的是装饰者又不做考虑,可能会出现问题。即:Decorator与它的Component不一样,Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识,而是应该依赖接口标识。
装饰者模式与代理模式的区别
相同点
这两种模式都描述了怎样为对象提供一定程度上的间接引用,proxy和decorator对象的实现部分都保留了指向另一个对象的引用,它们向这个对象发送请求。像Decorator模式一样,Proxy模式构成一个对象并为用户提供一致的接口。
不同点
但与Decorator模式不同的是,Proxy模式不能动态地添加或分离性质,它也不是为递归组合而设计的。它的目的是,当直接访问一个实体不方便或不符合需要时,为这个实体提供一个替代者,例如,实体在远程设备上,访问受到限制或者实体是持久存储的。
在Proxy模式中,实体定义了关键功能,而Proxy提供(或拒绝)对它的访问。在Decorator模式中,组件仅提供了部分功能,而一个或多个Decorator负责完成其他功能。Decorator模式适用于编译时不能(至少不方便)确定对象的全部功能的情况。这种开放性使递归组合成为Decorator模式中一个必不可少的部分。而在Proxy模式中则不是这样,因为Proxy模式强调一种关系(Proxy与它的实体之间的关系),这种关系可以静态的表达。模式间的这些差异非常重要,因为它们针对了面向对象设计过程中一些特定的经常发生问题的解决方法。
结合使用
但这并不意味着这些模式不能结合使用。可以设想有一个proxy-decorator,它可以给proxy添加功能,或是一个decorator-proxy用来修饰一个远程对象。尽管这种混合可能有用(我们手边还没有现成的例子),但它们可以分割成一些有用的模式。
在我看来Java中的动态代理更像是一个proxy-decorator,也就是代理模式与装饰者模式结合使用。而Java中的静态代理才是我们上面说的那种代理。
适用性
以下情况使用Decorator模式
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 处理那些可以撤消的职责。
- 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类,比如final修饰的类或者方法。