装饰器模式的定义与特点
装饰器(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
装饰器模式的主要优点有:
- 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
- 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果
- 装饰器模式完全遵守开闭原则
其主要缺点是:装饰器模式会增加许多子类,过度使用会增加程序得复杂性。
装饰器模式的结构与实现
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰器模式的目标。下面来分析其基本结构和实现方法。
模式的结构
装饰器模式主要包含以下角色。
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
装饰器模式的结构图如图 1 所示。

图1 装饰器模式的结构图
在Java标准库中,InputStream
是抽象类,FileInputStream
、ServletInputStream
、Socket.getInputStream()
这些InputStream
都是最终数据源。
现在,如果要给不同的最终数据源增加缓冲功能、计算签名功能、加密解密功能,那么,3个最终数据源、3种功能一共需要9个子类。如果继续增加最终数据源,或者增加新功能,子类会爆炸式增长,这种设计方式显然是不可取的。
Decorator模式的目的就是把一个一个的附加功能,用Decorator的方式给一层一层地累加到原始数据源上,最终,通过组合获得我们想要的功能。
例如:给FileInputStream
增加缓冲和解压缩功能,用Decorator模式写出来如下:
// 创建原始的数据源:
InputStream fis = new FileInputStream("test.gz");
// 增加缓冲功能:
InputStream bis = new BufferedInputStream(fis);
// 增加解压缩功能:
InputStream gis = new GZIPInputStream(bis);
或者一次性写成这样:
InputStream input = new GZIPInputStream( // 第二层装饰
new BufferedInputStream( // 第一层装饰
new FileInputStream("test.gz") // 核心功能
));
观察BufferedInputStream
和GZIPInputStream
,它们实际上都是从FilterInputStream
继承的,这个FilterInputStream
就是一个抽象的Decorator。我们用图把Decorator模式画出来如下:
┌───────────┐
│ Component │
└───────────┘
▲
┌────────────┼─────────────────┐
│ │ │
┌───────────┐┌───────────┐ ┌───────────┐
│ComponentA ││ComponentB │... │ Decorator │
└───────────┘└───────────┘ └───────────┘
▲
┌──────┴──────┐
│ │
┌───────────┐ ┌───────────┐
│DecoratorA │ │DecoratorB │...
└───────────┘ └───────────┘
最顶层的Component是接口,对应到IO的就是InputStream
这个抽象类。ComponentA、ComponentB是实际的子类,对应到IO的就是FileInputStream
、ServletInputStream
这些数据源。Decorator是用于实现各个附加功能的抽象装饰器,对应到IO的就是FilterInputStream
。而从Decorator派生的就是一个一个的装饰器,它们每个都有独立的功能,对应到IO的就是BufferedInputStream
、GZIPInputStream
等。
Decorator模式有什么好处?它实际上把核心功能和附加功能给分开了。核心功能指FileInputStream
这些真正读数据的源头,附加功能指加缓冲、压缩、解密这些功能。如果我们要新增核心功能,就增加Component的子类,例如ByteInputStream
。如果我们要增加附加功能,就增加Decorator的子类,例如CipherInputStream
。两部分都可以独立地扩展,而具体如何附加功能,由调用方自由组合,从而极大地增强了灵活性。
如果我们要自己设计完整的Decorator模式,应该如何设计?
我们还是举个栗子:假设我们需要渲染一个HTML的文本,但是文本还可以附加一些效果,比如加粗、变斜体、加下划线等。为了实现动态附加效果,可以采用Decorator模式。
首先,仍然需要定义顶层接口TextNode
:
public interface TextNode {
// 设置text:
void setText(String text);
// 获取text:
String getText();
}
对于核心节点,例如<span>
,它需要从TextNode
直接继承:
public class SpanNode implements TextNode { private String text; public void setText(String text) { this.text = text; } public String getText() { return "<span>" + text + "</span>"; } }
紧接着,为了实现Decorator模式,需要有一个抽象的Decorator类:
public abstract class NodeDecorator implements TextNode { protected final TextNode target; protected NodeDecorator(TextNode target) { this.target = target; } public void setText(String text) { this.target.setText(text); } }
这个NodeDecorator
类的核心是持有一个TextNode
,即将要把功能附加到的TextNode
实例。接下来就可以写一个加粗功能:
public class BoldDecorator extends NodeDecorator { public BoldDecorator(TextNode target) { super(target); } public String getText() { return "<b>" + target.getText() + "</b>"; } }
类似的,可以继续加ItalicDecorator
、UnderlineDecorator
等。客户端可以自由组合这些Decorator:
TextNode n1 = new SpanNode(); TextNode n2 = new BoldDecorator(new UnderlineDecorator(new SpanNode())); TextNode n3 = new ItalicDecorator(new BoldDecorator(new SpanNode())); n1.setText("Hello"); n2.setText("Decorated"); n3.setText("World"); System.out.println(n1.getText()); // 输出<span>Hello</span> System.out.println(n2.getText()); // 输出<b><u><span>Decorated</span></u></b> System.out.println(n3.getText()); // 输出<i><b><span>World</span></b></i>