文章目录
装饰者(Decorator)模式
隶属类别——对象结构型模式
1. 意图
动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator模式相比生成子类更为灵活。
2. 别名
包装器Wrapper
3. 动机
有时我们希望给某个对象而不是整个类添加一些功能。例如,一个图形用户界面工具箱允许你对任意一个用户界面组件添加一个特性,例如边框,或是一些行为,例如窗口滚动。
使用继承机制是添加功能的有限途径,从其他类继承过来的边框特性可以被多个子类的实例所使用。但这种方法不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的方式和时机。
一种较为灵活的方式是将组件嵌入另一个对象中,由这个对象添加边框。我们称这个嵌入的对象为装饰。这个装饰与它所装饰的组件接口一致,因为它对使用该组件的客户透明。它将客户请求转发给该组件,并且可能在转发前后执行一些额外的动作(例如画一个边框)。透明性使得你可以递归的嵌套多个装饰,从而可以添加任意多的功能,如下图所示。
例如,假定有一个对象TextView,它可以在窗口中显示正文。缺省的TextView没有滚动条,因为我们可能有时并不需要滚动条。当需要滚动条是,我们可以用ScrollDecorator添加滚动条。如果我们还想在TextView周围添加一个粗黑边框。可以使用BorderDecorator添加。因此只要简单地将这些装饰和TextView进行组合,就可以达到预期的效果。
下面的对象图展示了如何将一个TextView对象与BorderDecorator以及ScrollDecorator对象组合组装起来产生一个具有边框和滚动条的文本显示窗口。
VisualComPonent是一个描述可视对象的抽象类,它定义了绘制和事件处理的接口。注意Decorator类怎么将绘制请求简单地发送给它的组件(其实可以不处理),以及Decorator如何扩展这个操作。
Decorator的子类为特定功能可以自由地添加一些操作。例如,如果其它对象知道界面中恰好有一个ScrollDecorator对象,这些对象就可以用ScrollDecorator对象的ScrollTo操作滚动这个界面。这个模式中有一点很重要,它使得在VisualComponent可以出现的任何地方都可以装饰。因此。客户通常不会感觉装饰过的组件和为装饰组件之间的差异,也不会与装饰产生任何依赖关系。
4. 适用性
以下情况下适用Decorator模式
- 在不影响其他对象的情况下,以动态,透明的方法给单个对象添加职责。
- 处理那些可以撤销的职责
- 当不能采用生成子类的方法进行扩充时。一种情况时,可能有大量独立的扩展,为支持每一个组合将产生大量的子类,使得子类数目呈爆炸性增长(比如各种调料比例组合的饮料)。另一种情况可能是因为类定义被隐藏(例如构造器是私有),或类定义不能生成子类。
5. 结构
6. 参与者
-
Component(VisualComponent)
——定义一个对象接口(抽象方法,入口),可以给这些对象动态添加职责。
-
ConcreteComponent(TextView)
——定义一个对象,可以给这个对象添加一些职责
-
Decorator
——维持一个指向Component对象的引用(其实也可以不用,只能使用其子类控制也行,因为Decorator是抽象类不能被实例化,所以不用控制引用的同时,),并定义一个与Component接口一致的接口。
-
ConcreteDecorator(BorderDecorator,ScrollDecorator)
——向组件添加职责
7. 协作
- Decorator将请求转发给它的Componnet对象,并有可能在转发请求前后执行一些附加的动作。
8. 效果
Decorator模式至少有两个主要的优点和两个缺点
优点:
-
1.比静态继承更灵活 与对象的静态继承(或者Java中不支持的多重继承)相比,Decorator模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。关于分离方法:我终于找到例子了!!!例如JDK中的Throwable.printStackTrace()的几个重载方法:如下:
public void printStackTrace() { printStackTrace(System.err); } /** * Prints this throwable and its backtrace to the specified print stream. * * @param s {@code PrintStream} to use for output */ public void printStackTrace(PrintStream s) { printStackTrace(new WrappedPrintStream(s)); } /** * Prints this throwable and its backtrace to the specified * print writer. * * @param s {@code PrintWriter} to use for output * @since JDK1.1 */ public void printStackTrace(PrintWriter s) { printStackTrace(new WrappedPrintWriter(s)); } private void printStackTrace(PrintStreamOrWriter s) { // Guard against malicious overrides of Throwable.equals by // using a Set with identity equality semantics. Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>()); dejaVu.add(this); synchronized (s.lock()) { // Print our stack trace s.println(this); StackTraceElement[] trace = getOurStackTrace(); for (StackTraceElement traceElement : trace) s.println("\tat " + traceElement); // Print suppressed exceptions, if any for (Throwable se :