装饰器模式
- 解决问题
- 装饰器有很多种,可以一次用一个或者多个,继承方式会使得类数目爆炸。
- 角色
- 具体组件 + 装饰器类
- 经典例子
- Java IO 库,各种输入输出流
1 解决的问题
当前招聘信息的发布,就可以抽象为一个装饰器模式。对于笔试部分,一般为了让面试者确切收到笔试消息,会通过多种途径进行提示,微信公众号平台、短信、邮箱等等。如果将这三种发送方式进行组合的话,就是 2^(3)-1=7 种组合,即 7 种要创建的发送信息的对象。
之前,如果没有较好设计的话,就涉及 7 种组合,分别发送。那么更好的设计是,将发送功能抽取出来,作为一个接口。微信公众号平台、短信、邮箱等去实现该接口的发送功能。之后再创建一个装饰器类,根据可行的发送方式,将对应的实现类注入到装饰器类的成员变量中。装饰器类同样实现发送方法,该发送方法调用其成员变量的发送方法。
这样就可以使得需要创建的类大幅下降。
除此之外,还有新增功能的装饰器模式。
通过继承的方式来增强类深度角度的特性,太复杂臃肿。如下图中,想实现 SMS、Wechat 、QQ 中一个或多个消息通知发生,通过继承来实现,类太多了。

- 希望增强原有的特性
- 希望添加新的特性
2 装饰器模式
允许动态地向一个现有的对象添加新功能或增强已有功能,同时不改变其结构,相当于对现有对象的进行了一个包装。
四个角色:
首先,分为两大部分,分别是组件和装饰。组件就是平时开发时的各种对象,包含抽象组件和具体组件。装饰就是对组件进行扩展,包含抽象装饰和具体装饰。
- Component (抽象组件) → 需要被改进的对象 & 客户端要使用的对象,内部声明需要子类实现的业务方法。
- ConcreteComponent (具体组件) → 即具体的应用对象、抽象组件的具体实现。
- Decorator (抽象装饰类) → 继承/实现抽象组件,重写抽象组件的方法来增强已有功能;内部包含抽象组件对象。
- ConcreteDecorator (具体装饰类) → 抽象装饰类的具体实现,除了重写方法外,还可以添加附加功能;
3 实现代码
3.1 增强原有的功能
// 抽象组件(接口和抽象类都可以)
interface IShape {
String show();
}
// 抽象装饰类(内部有一个指向组件对象的引用,用来调装饰前对象的方法)
public abstract class BaseDecorator implements IShape {
private IShape shape;
public BaseDecorator(IShape shape) { this.shape = shape; }
@Override public String show() { return shape.show(); }
}
// 具体组件类
public class CircleShape implements IShape {
@Override public String show() { return "圆形"; }
}
public class SquareShape implements IShape {
@Override public String show() { return "矩形"; }
}
// 颜色具体装饰类(可调用抽象装饰类中定义的方法,也可新增方法来扩展对象行为)
public class RedDecorator extends BaseDecorator {
public RedDecorator(IShape shape) { super(shape); }
@Override public String show() { return "红色" + super.show(); }
}
public class BlueDecorator extends BaseDecorator {
public BlueDecorator(IShape shape) { super(shape); }
@Override public String show() { return "蓝色" + super.show(); }
}
// 材质具体装饰类
public class SmoothDecorator extends BaseDecorator {
public SmoothDecorator(IShape shape) { super(shape); }
@Override public String show() { return "光滑" + super.show(); }
}
public class MatteDecorator extends BaseDecorator {
public MatteDecorator(IShape shape) { super(shape); }
@Override public String show() { return "磨砂" + super.show(); }
}
// 大小具体装饰类
public class BigDecorator extends BaseDecorator {
public BigDecorator(IShape shape) { super(shape); }
@Override public String show() { return "大" + super.show(); }
}
public class MiddleDecorator extends BaseDecorator {
public MiddleDecorator(IShape shape) { super(shape); }
@Override public String show() { return "中" + super.show(); }
}
public class SmallDecorator extends BaseDecorator {
public SmallDecorator(IShape shape) { super(shape); }
@Override public String show() { return "小" + super.show(); }
}
// 测试用例
public class DecoratorTest {
public static void main(String[] args) {
IShape circle = new CircleShape();
IShape square = new SquareShape();
IShape redCircle = new RedDecorator(circle);
IShape smoothBlueSquare = new SmoothDecorator(new BlueDecorator(square));
IShape bigMatteRedCircle = new BigDecorator(new MatteDecorator(redCircle));
System.out.println(circle.show());
System.out.println(square.show());
System.out.println(redCircle.show());
System.out.println(smoothBlueSquare.show());
System.out.println(bigMatteRedCircle.show());
}
}
用继承要 24 个子类,用装饰器模式只要 10 个,顺带画出 UML 类图:

3.2 用于添加功能的装饰模式
我们用程序来模拟一下房屋装饰粘钩后,新增了挂东西功能的过程:
新建房屋接口:
public interface IHouse {
void live();
}房屋类:
public class House implements IHouse{
@Override
public void live() {
System.out.println("房屋原有的功能:居住功能");
}
}新建粘钩装饰器接口,继承自房屋接口:
public interface IStickyHookHouse extends IHouse{
void hangThings();
}粘钩装饰类:(这里被装饰者 IHouse 是透明的)
public class StickyHookDecorator implements IStickyHookHouse {
private final IHouse house;
public StickyHookDecorator(IHouse house) { this.house = house; }
@Override public void live() { house.live(); }
@Override public void hangThings() { System.out.println("有了粘钩后,新增了挂东西功能"); }
}客户端测试:
public class Client {
@Test
public void show() {
IHouse house = new House();
house.live();
IStickyHookHouse stickyHookHouse = new StickyHookDecorator(house);
stickyHookHouse.live();
stickyHookHouse.hangThings();
}
}运行程序,显示如下:
房屋原有的功能:居住功能
房屋原有的功能:居住功能
有了粘钩后,新增了挂东西功能没有修改原有的功能,只是扩展了新的功能,这种模式在装饰模式中称之为 半透明装饰模式。
- 因为装饰后的接口具有原接口不具有的方法,因此客户端使用时要区别对待,新接口是可见的、不透明的。
- 因为被装饰者可以是实现了原接口的任意对象,所以被装饰者对客户端是不可见的、透明的。
半透明装饰模式中,我们无法多次装饰,因为将添加新功能后的类 A 再次传入其他装饰类 B 中时,B 类是并不认识 A 类中添加的新功能,所以无法进行多次装饰。
只要添加了新功能的装饰模式都称之为 半透明装饰模式,他们都具有不可以多次装饰的特点。既增强了功能,又添加了新功能的装饰模式仍然具有半透明特性。
4 使用场景
- 在不影响其他对象的情况下,快速动态透明地为单个对象添加功能;
- 动态地增强对象的特性 or 添加功能,链式地;
- 不支持继承扩展类的场景,如
final关键字修饰的类;
5 优缺点
优点:
- 快速扩展对象功能,比继承灵活,不会导致类个数急剧增加,动态增删对象实例功能,按需按顺序组合功能;
缺点:
- 顺序调用链装饰时,删除某个装饰器需要修改上下文代码;
- 容易增加很多装饰对象,增加代理理解难度;
- 和桥接模式一样,组合相比继承,更不容易找到对象间的调用关系;
6 经典应用例子
6.1 Java IO 类库
IO 读入的方法与装饰器模式很像,其实背后也用到了装饰器模式。
InputStream in = new BufferedInputStream(new FileInputStream("src/readme.txt"));InputStream 的继承关系如下:

这个图和上面例子中的图大致可以对应上。其中,InputStream 是一个抽象类,对应上文例子中的 IShape,其中最重要的方法是 read 方法,这是一个抽象方法:
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException;
// ...
}上图中,左边的三个类 FileInputStream 、 ByteArrayInputStream 、 ServletInputStream 是 InputStream 的三个子类,对应上文例子中实现了 IShape 接口的各种形状。
右下角的三个类 BufferedInputStream 、 DataInputStream 、 CheckedInputStream 是三个具体的装饰者类,他们都为 InputStream 增强了原有功能或添加了新功能。
FilterInputStream 是所有装饰类的父类,它没有实现具体的功能,仅用来包装了一下 InputStream:
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) { this.in = in; }
public int read() throws IOException { return in.read(); }
//...
}这里生成装饰类的父类
FilterInputS tream的原因在于:
有些装饰器本身不需要真正处理 read()等方法,但是装饰器模式的 链式传递,不用到也要实现这些方法。而每个这样的装饰器都重写方法的话,会存在大量重复代码。用一个装饰器父类FilterInputStream提供默认实现,以此减少这些重复代码。
我们以 BufferedInputStream 为例。原有的 InputStream 读取文件时,是一个字节一个字节读取的,这种方式的执行效率并不高,所以我们可以设立一个缓冲区,先将内容读取到缓冲区中,缓冲区读满后,将内容从缓冲区中取出来,这样就变成了一段一段读取,用内存换取效率。BufferedInputStream 就是用来做这个的。它继承自 FilterInputStream :
public class BufferedInputStream extends FilterInputStream {
private static final int DEFAULT_BUFFER_SIZE = 8192;
protected volatile byte buf[];
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
//...
}我们先来看它的构造方法,在构造方法中,新建了一个 byte[] 作为缓冲区,从源码中我们看到,Java 默认设置的缓冲区大小为 8192 byte,也就是 8 KB。
然后我们来查看 read 方法:
public class BufferedInputStream extends FilterInputStream {
//...
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
private void fill() throws IOException {
// 往缓冲区内填充读取内容的过程
//...
}
}在 read 方法中,调用了 fill 方法,fill 方法的作用就是往缓冲区中填充读取的内容。这样就实现了增强原有的功能。
在源码中我们发现,BufferedInputStream 没有添加 InputStream 中没有的方法,所以 BufferedInputStream 使用的是 透明的装饰模式。
DataInputStream 用于更加方便地读取 int、double 等内容,观察 DataInputStream 的源码可以发现,DataInputStream 中新增了 readInt、readLong 等方法,所以 DataInputStream 使用的是 半透明装饰模式。