这是 2020 年 4 月整理的设计模式专题第六篇。装饰者模式是结构型模式里”加料”思想的代表——像煎饼摊加蛋加肠一样,给对象动态叠加功能。JDK 的 I/O 流是它的经典应用。
简单说明
装饰者模式(Decorator Pattern):在不改变原有对象的基础上将功能附加到对象上,提供了比继承更有弹性的替代方案。
- 扩展原有对象的功能,这些功能的添加是动态的;
- 还可以动态的撤销;
- 经典实现:JDK 的 I/O 操作(
BufferedInputStream装饰FileInputStream等)。
代码实现
有一个小吃摊,现对煎饼的售卖扩展:
/**
* 描述:抽象的小吃类
*/
public abstract class Snack {
protected abstract String name();
protected abstract int price();
}
/**
* 描述:煎饼(具体组件)
*/
public class Battercake extends Snack {
@Override
protected String name() {
return "普通煎饼";
}
@Override
protected int price() {
return 10;
}
}
/**
* 描述:抽象的装饰者
*/
public abstract class AbstractDecorator extends Snack {
private Snack snack;
public AbstractDecorator(Snack battercake) {
this.snack = battercake;
}
@Override
protected String name() {
return snack.name();
}
@Override
protected int price() {
return snack.price();
}
}
/**
* 描述:装饰者实现类,增强功能:加蛋
*/
public class EggDecotator extends AbstractDecorator {
public EggDecotator(Snack snack) {
super(snack);
}
@Override
protected String name() {
return super.name() + " 加一个蛋";
}
@Override
protected int price() {
return super.price() + 2;
}
}
/**
* 描述:测试类
*/
public class DecoratorTest {
public static void main(String[] args) {
Snack battercake = new Battercake();
battercake = new EggDecotator(battercake);
battercake = new EggDecotator(battercake);
battercake = new EggDecotator(battercake);
System.out.println(battercake.name() + " , " + battercake.price());
}
}
输出:普通煎饼 加一个蛋 加一个蛋 加一个蛋 , 16
四个角色:
- 抽象组件(Snack):定义被装饰对象的接口;
- 具体组件(Battercake):被装饰的原始对象;
- 抽象装饰者(AbstractDecorator):持有组件引用,转发所有方法;
- 具体装饰者(EggDecotator):在转发基础上增强功能(加蛋 +2 元)。
动态叠加的秘密:装饰者层层包裹——new EggDecotator(new EggDecotator(new EggDecotator(new Battercake()))),每层只在上一层基础上加一点功能,且运行时自由组合、随时撤销。
装饰者 vs 继承
| 对比项 | 继承 | 装饰者 |
|---|---|---|
| 扩展方式 | 编译期静态绑定 | 运行期动态组合 |
| 组合爆炸 | 加蛋/加肠/加火腿的排列组合 → 类爆炸 | 任意叠加,类数量线性 |
| 撤销 | 不可撤销 | 可以撤销(去掉一层包装) |
| 客户端感知 | 需要知道具体子类 | 面向抽象组件编程 |
为什么说比继承有弹性:如果煎饼摊要卖”加蛋、加肠、加火腿”的任意组合,继承需要为每个组合建一个子类(2³ = 8 个);装饰者只需要 3 个装饰类,任意排列组合。
适用场景与优缺点
适用场景:
- 在不影响其他对象的情况下,以透明、动态的方式给单个对象添加功能;
- 需要动态撤销功能;
- 不能采用继承或继承会导致类爆炸。
优点:
- 比继承更灵活,动态增强、动态撤销;
- 符合开闭原则(新装饰者不影响已有类);
- 装饰者与被装饰者解耦,符合合成复用原则。
缺点:
- 多层装饰会增加系统的复杂性(出现很多小对象);
- 排错困难:层层包装后,定位问题需要逐层剥开。
现实中的应用
| 场景 | 说明 |
|---|---|
| JDK I/O 流 | new BufferedInputStream(new FileInputStream(...)) 层层装饰 |
| Servlet API | HttpServletRequestWrapper / HttpServletResponseWrapper |
| Spring 事务 | 对 Service 方法动态增强(本质是 AOP,装饰思想的扩展) |
| MyBatis | Cache(装饰者叠加多种缓存行为) |
小结
- 装饰者模式在不改变原有对象的基础上动态附加功能,比继承更有弹性。
- 四个角色:抽象组件、具体组件、抽象装饰者、具体装饰者。
- 核心手法:装饰者持有组件引用,层层包裹,动态叠加。
- 符合开闭原则和合成复用原则,但多层包装会增加复杂度与排错难度。
- 一句话:继承是”做一种新口味”,装饰者是”往现有口味上随意加料”。