装饰器设计模式(Decorator Pattern)详解
本文档系统讲解装饰器模式的定义、原理、实现方式、应用场景以及在 JDK 源码中的典型应用。
一、模式概述
1.1 什么是装饰器模式
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为,而无需修改原有对象的结构。
本质:在不改变原有类的前提下,动态地给一个对象添加一些额外的职责。就增加功能而言,装饰器模式相比生成子类更为灵活。
1.2 主要意图
装饰器模式的五大核心意图:
| 编号 | 意图 | 说明 |
|---|---|---|
| 1 | 动态扩展 | 在运行时动态地为对象添加职责,避免编译期绑定 |
| 2 | 替代多继承 | 通过组合方式实现功能扩展,避免类爆炸 |
| 3 | 灵活组合 | 可以自由组合多个装饰器,形成链式装饰 |
| 4 | 开闭原则 | 对扩展开放,对修改关闭 |
| 5 | 职责分离 | 每个装饰器只关心自己新增的职责 |
1.3 应用领域
装饰器模式广泛应用于以下场景:
- Java I/O 流:BufferedReader 包装 FileReader、DataInputStream 包装 FileInputStream
- Java Collections:Collections.synchronizedList、Collections.unmodifiableList
- GUI 组件:Swing 中为组件添加滚动条、边框
- Web 框架:Servlet 中的 RequestWrapper、ResponseWrapper
- 中间件:Spring 中的 HandlerInterceptor、MyBatis 中的 Cache
二、核心角色与结构
2.1 四类核心角色
装饰器模式涉及以下四个核心角色:
| 角色 | 名称 | 职责 |
|---|---|---|
| Component | 抽象组件 | 定义被装饰对象的公共接口 |
| ConcreteComponent | 具体组件 | 被装饰的原始对象,实现抽象组件接口 |
| Decorator | 抽象装饰器 | 持有抽象组件引用,实现抽象组件接口,是所有具体装饰器的父类 |
| ConcreteDecorator | 具体装饰器 | 为组件添加具体的额外职责 |
2.2 UML 类图
2.3 结构示意
三、Java 代码实现
3.1 场景设定
以咖啡订单系统为例:顾客可以点一杯咖啡,并选择性地添加牛奶、糖、摩卡等多种调料,每种调料都会增加相应的费用。
这是装饰器模式的经典教学案例,因为调料的组合数量可能非常多(每加一种调料都生成一个子类会导致类爆炸),非常适合使用装饰器模式。
3.2 抽象组件:饮料
/**
* 抽象组件:所有饮料和调料装饰器的统一接口
*/
public abstract class Beverage {
/** 饮料描述 */
protected String description = "未知饮料";
/**
* 获取饮料描述
* @return 描述信息
*/
public String getDescription() {
return description;
}
/**
* 计算饮料费用,由子类实现
* @return 总费用
*/
public abstract double cost();
}
3.3 具体组件:具体饮料
/**
* 具体组件:浓缩咖啡
*/
public class Espresso extends Beverage {
public Espresso() {
description = "浓缩咖啡";
}
@Override
public double cost() {
return 18.0;
}
}
/**
* 具体组件:美式咖啡
*/
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "深焙咖啡";
}
@Override
public double cost() {
return 15.0;
}
}
/**
* 具体组件:脱因咖啡
*/
public class Decaf extends Beverage {
public Decaf() {
description = "脱因咖啡";
}
@Override
public double cost() {
return 16.0;
}
}
/**
* 具体组件:综合咖啡
*/
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "综合咖啡";
}
@Override
public double cost() {
return 12.0;
}
}
3.4 抽象装饰器:调料基类
/**
* 抽象装饰器:所有调料装饰器的父类
* 关键设计:继承 Beverage 并持有 Beverage 引用
*/
public abstract class CondimentDecorator extends Beverage {
/** 被装饰的饮料对象 */
protected Beverage beverage;
/**
* 子类必须重新实现描述方法
* @return 加入调料后的完整描述
*/
@Override
public abstract String getDescription();
}
3.5 具体装饰器:具体调料
3.5.1 牛奶调料
/**
* 具体装饰器:牛奶
*/
public class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 牛奶";
}
@Override
public double cost() {
return beverage.cost() + 3.0;
}
}
3.5.2 摩卡调料
/**
* 具体装饰器:摩卡(巧克力风味)
*/
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 摩卡";
}
@Override
public double cost() {
return beverage.cost() + 4.5;
}
}
3.5.3 糖调料
/**
* 具体装饰器:糖
*/
public class Sugar extends CondimentDecorator {
public Sugar(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 糖";
}
@Override
public double cost() {
return beverage.cost() + 1.0;
}
}
3.5.4 奶泡调料
/**
* 具体装饰器:奶泡
*/
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + " + 奶泡";
}
@Override
public double cost() {
return beverage.cost() + 3.5;
}
}
3.5.5 大杯装饰器
/**
* 具体装饰器:大杯规格
*/
public class LargeSize extends CondimentDecorator {
public LargeSize(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return "大杯 " + beverage.getDescription();
}
@Override
public double cost() {
return beverage.cost() + 5.0;
}
}
3.6 客户端调用
/**
* 装饰器模式客户端演示
*/
public class StarbuzzCoffee {
public static void main(String[] args) {
// ===== 场景 1:基础浓缩咖啡 =====
Beverage beverage1 = new Espresso();
printOrder("订单 1", beverage1);
// ===== 场景 2:深焙 + 双份摩卡 + 奶泡 =====
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
printOrder("订单 2", beverage2);
// ===== 场景 3:综合咖啡 + 牛奶 + 糖 =====
Beverage beverage3 = new HouseBlend();
beverage3 = new Milk(beverage3);
beverage3 = new Sugar(beverage3);
printOrder("订单 3", beverage3);
// ===== 场景 4:大杯脱因 + 摩卡 + 牛奶 =====
Beverage beverage4 = new Decaf();
beverage4 = new LargeSize(beverage4);
beverage4 = new Mocha(beverage4);
beverage4 = new Milk(beverage4);
printOrder("订单 4", beverage4);
// ===== 场景 5:动态组合 =====
Beverage custom = new Sugar(new Milk(new Mocha(new Espresso())));
printOrder("订单 5", custom);
}
private static void printOrder(String orderName, Beverage beverage) {
System.out.println("=========================");
System.out.println(orderName + ":" + beverage.getDescription());
System.out.printf("%s 费用:%.2f 元%n", orderName, beverage.cost());
System.out.println("=========================");
}
}
3.7 运行结果
=========================
订单 1:浓缩咖啡
订单 1 费用:18.00 元
=========================
=========================
订单 2:深焙咖啡 + 摩卡 + 摩卡 + 奶泡
订单 2 费用:27.00 元
=========================
=========================
订单 3:综合咖啡 + 牛奶 + 糖
订单 3 费用:16.00 元
=========================
=========================
订单 4:大杯 脱因咖啡 + 摩卡 + 牛奶
订单 4 费用:28.50 元
=========================
=========================
订单 5:浓缩咖啡 + 摩卡 + 牛奶 + 糖
订单 5 费用:26.50 元
=========================
四、调用时序分析
4.1 链式装饰时序图
下面以订单 2「深焙咖啡 + 摩卡 + 摩卡 + 奶泡」为例,展示完整的调用链。
4.2 调用栈深度说明
对于订单 2,方法调用栈深度变化如下:
| 步骤 | 当前栈深度 | 正在执行的方法 | 已累计费用 |
|---|---|---|---|
| 1 | 1 | Whip.cost() | 0.0 |
| 2 | 2 | Mocha2.cost() | 0.0 |
| 3 | 3 | Mocha1.cost() | 0.0 |
| 4 | 4 | DarkRoast.cost() | 15.0 |
| 5 | 3 | Mocha1 累加返回 | 19.5 |
| 6 | 2 | Mocha2 累加返回 | 24.0 |
| 7 | 1 | Whip 累加返回 | 27.5 |
五、装饰器链与功能扩展
5.1 装饰器链可视化
5.2 装饰器 vs 继承:类数量对比
用一张饼图直观展示传统继承方式与装饰器模式在组合数量上的差异。假设有 4 种饮料和 3 种调料,每种调料都有/没有两种状态:
装饰器模式只需要 8 个类(4 种饮料 + 1 个抽象装饰器 + 3 种调料),而继承方式需要 4 × 2³ = 32 个类,是装饰器的 4 倍。当调料种类增加时,差距会进一步拉大。
5.3 装饰器模式扩展矩阵
下表展示了在 4 种基础饮料基础上装饰不同调料的可行性:
| 基础饮料 | 加牛奶 | 加摩卡 | 加糖 | 加奶泡 | 大杯 |
|---|---|---|---|---|---|
| 浓缩咖啡 | 支持 | 支持 | 支持 | 支持 | 支持 |
| 深焙咖啡 | 支持 | 支持 | 支持 | 支持 | 支持 |
| 脱因咖啡 | 支持 | 支持 | 支持 | 支持 | 支持 |
| 综合咖啡 | 支持 | 支持 | 支持 | 支持 | 支持 |
每种饮料都能搭配任意调料,无需新增类。
六、装饰器在 JDK 源码中的应用
6.1 Java I/O 流装饰器体系
Java I/O 是装饰器模式最经典的应用场景。InputStream、OutputStream、Reader、Writer 等都是抽象组件,大量的包装类都是具体装饰器。
6.2 JDK I/O 装饰器源码解析
6.2.1 FilterInputStream — 抽象装饰器
/**
* FilterInputStream 是典型的抽象装饰器
* 它本身实现 InputStream 接口,并持有一个 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();
}
}
6.2.2 BufferedInputStream — 具体装饰器
/**
* BufferedInputStream 是具体装饰器
* 为输入流添加缓冲功能
*/
public class BufferedInputStream extends FilterInputStream {
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
super(in);
// 初始化缓冲数组
buf = new byte[size];
}
/**
* 重写 read 方法,先尝试从缓冲区读取
* 这是装饰器"前增强"的典型表现
*/
@Override
public synchronized int read() throws IOException {
// 自定义缓冲逻辑
// ...
return in.read(); // 委托给被装饰流
}
}
6.2.3 DataInputStream — 具体装饰器
/**
* DataInputStream 是具体装饰器
* 为输入流添加读取基本数据类型的能力
*/
public class DataInputStream extends FilterInputStream implements DataInput {
public DataInputStream(InputStream in) {
super(in);
}
/**
* 新增的职责:读取 int
* 装饰器可以扩展接口方法
*/
public final int readInt() throws IOException {
int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
// ...
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}
}
6.2.4 客户端典型用法
/**
* 通过装饰器组合实现功能扩展
* 这是装饰器链的经典写法
*/
public class IODecoratorDemo {
public static void main(String[] args) throws IOException {
// 层层装饰,每一层都添加新职责
InputStream in = new FileInputStream("data.txt");
// 第 1 层装饰:添加缓冲功能
in = new BufferedInputStream(in);
// 第 2 层装饰:添加读取基本类型功能
in = new DataInputStream(in);
// 最终 in 同时具备 缓冲 + 基本类型读取 两种增强
DataInputStream dis = (DataInputStream) in;
int magic = dis.readInt();
String name = dis.readUTF();
}
}
6.3 Collections 工具类中的装饰器
java.util.Collections 提供了大量静态工厂方法返回装饰后的集合对象:
| 装饰器方法 | 返回类型 | 添加的职责 |
|---|---|---|
Collections.synchronizedList(List) |
SynchronizedList |
线程安全 |
Collections.unmodifiableList(List) |
UnmodifiableList |
不可修改 |
Collections.checkedList(List, Class) |
CheckedList |
类型安全检查 |
Collections.synchronizedMap(Map) |
SynchronizedMap |
线程安全 |
源码示例:
/**
* Collections.synchronizedList 装饰器源码
*/
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess)
? new SynchronizedRandomAccessList<>(list)
: new SynchronizedList<>(list);
}
/**
* SynchronizedList 内部通过 synchronized 块包装原有方法
*/
static class SynchronizedList<E> extends SynchronizedCollection<E>
implements List<E> {
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
@Override
public E get(int index) {
synchronized (mutex) { // 添加同步职责
return list.get(index);
}
}
@Override
public E set(int index, E element) {
synchronized (mutex) {
return list.set(index, element);
}
}
}
七、装饰器模式与相关模式对比
7.1 与继承机制对比
| 对比维度 | 继承 | 装饰器 |
|---|---|---|
| 灵活性 | 编译期决定,无法动态扩展 | 运行时动态扩展 |
| 类数量 | 组合爆炸,类数量指数级增长 | 按职责独立成类 |
| 复用性 | 难以复用具体行为 | 各装饰器自由组合复用 |
| 耦合度 | 与父类耦合度高 | 通过接口解耦 |
| 违反原则 | 易违反开闭原则 | 符合开闭原则 |
7.2 与代理模式对比
| 对比维度 | 装饰器模式 | 代理模式 |
|---|---|---|
| 目的 | 为对象增加新职责 | 控制对真实对象的访问 |
| 创建时机 | 客户端显式包装 | 通常由框架自动创建 |
| 关注点 | 增强功能 | 访问控制、延迟加载、远程调用等 |
| 典型应用 | IO 流装饰、调料组合 | Spring AOP、JDK 动态代理 |
7.3 与适配器模式对比
| 对比维度 | 装饰器模式 | 适配器模式 |
|---|---|---|
| 主要目的 | 增强现有功能 | 转换接口使之兼容 |
| 是否改变接口 | 不改变接口 | 将一个接口转换为另一个接口 |
| 关系 | 同接口层层包装 | 包装后暴露新的接口 |
| 适用场景 | 动态叠加职责 | 复用已有但接口不匹配的类 |
八、优缺点分析
8.1 优点
- 灵活性高:运行时动态地给对象添加职责,比继承更灵活
- 遵循开闭原则:无需修改原有类即可扩展功能
- 避免类爆炸:通过组合代替继承,避免子类数量的指数级增长
- 职责分明:每个装饰器只负责一个增强点,符合单一职责原则
- 可自由组合:装饰器之间可以任意嵌套组合,形成链式增强
- 运行时动态撤销:装饰器对象只是包装,理论上可以动态替换或移除
8.2 缺点
- 类数量增加:每增加一个装饰职责都需要一个新类
- 调试相对困难:多层装饰会增加调用栈深度,调试时需要逐层剥离
- 类型签名易混乱:装饰器链很长时,类型签名看起来较复杂
- 装饰器顺序敏感:某些场景下装饰器的顺序会影响结果
- 初始化成本:多层装饰的创建会增加少量开销
8.3 优缺点饼图分布
九、实战应用场景
9.1 适用场景清单
装饰器模式特别适用于以下场景:
| 场景 | 说明 |
|---|---|
| 动态透明地给对象添加职责 | 不影响其他对象 |
| 职责可撤销 | 通过撤销包装即可撤销增强 |
| 难以或不适合用继承扩展 | 继承会导致大量子类 |
| 需要多次组合不同增强 | 装饰器可以任意嵌套 |
| 符合开闭原则的项目 | 对扩展开放对修改关闭 |
9.2 典型业务场景
9.3 业务场景:订单运费叠加
实际业务中运费计算往往是组合式的:基础运费 + 偏远地区附加费 + 加急附加费 + 包装费。
/**
* 抽象组件:运费计算器
*/
public interface ShippingCost {
double calculate(Order order);
String description();
}
/**
* 具体组件:基础运费
*/
public class BaseShipping implements ShippingCost {
@Override
public double calculate(Order order) {
return 10.0;
}
@Override
public String description() {
return "基础运费";
}
}
/**
* 抽象装饰器
*/
public abstract class ShippingDecorator implements ShippingCost {
protected ShippingCost wrapped;
public ShippingDecorator(ShippingCost wrapped) {
this.wrapped = wrapped;
}
}
/**
* 具体装饰器:偏远地区附加费
*/
public class RemoteAreaFee extends ShippingDecorator {
public RemoteAreaFee(ShippingCost wrapped) {
super(wrapped);
}
@Override
public double calculate(Order order) {
return wrapped.calculate(order) + (order.isRemote() ? 15.0 : 0.0);
}
@Override
public String description() {
return wrapped.description() + " + 偏远附加";
}
}
/**
* 具体装饰器:加急附加费
*/
public class ExpressFee extends ShippingDecorator {
public ExpressFee(ShippingCost wrapped) {
super(wrapped);
}
@Override
public double calculate(Order order) {
return wrapped.calculate(order) + 20.0;
}
@Override
public String description() {
return wrapped.description() + " + 加急";
}
}
/**
* 具体装饰器:包装费
*/
public class GiftWrapFee extends ShippingDecorator {
public GiftWrapFee(ShippingCost wrapped) {
super(wrapped);
}
@Override
public double calculate(Order order) {
return wrapped.calculate(order) + (order.isGift() ? 5.0 : 0.0);
}
@Override
public String description() {
return wrapped.description() + " + 礼品包装";
}
}
/**
* 客户端使用:动态组合运费
*/
public class ShippingClient {
public static void main(String[] args) {
Order order = new Order();
order.setRemote(true);
order.setGift(true);
// 动态组装:基础 + 偏远 + 礼品包装
ShippingCost cost = new GiftWrapFee(
new RemoteAreaFee(
new BaseShipping()));
System.out.println("运费明细:" + cost.description());
System.out.println("总运费:" + cost.calculate(order) + " 元");
}
}
十、使用装饰器模式的注意事项
10.1 设计要点
| 要点 | 建议 |
|---|---|
| 接口设计 | 抽象组件应当尽量保持接口精简 |
| 类型透明 | 装饰器与被装饰对象具有相同的接口 |
| 抽象装饰器 | 必须存在抽象装饰器类,否则无法扩展 |
| 链式调用 | 装饰链不要太深(建议控制在 5 层以内) |
| 状态隔离 | 各装饰器的状态尽量独立,避免相互污染 |
| equals 与 hashCode | 重写时要注意装饰链上的调用 |
10.2 常见陷阱
| 陷阱 | 后果 | 解决方案 |
|---|---|---|
| 抽象装饰器继承具体类 | 无法再装饰其他类型 | 必须继承抽象组件 |
| 装饰器重复包装 | 出现性能损耗与逻辑异常 | 添加标志位或工厂方法 |
| 装饰链过深 | 调试困难、栈溢出风险 | 用工厂方法或 Builder 模式封装 |
| 接口污染 | 在装饰器中加入太多方法 | 装饰器只关注新增职责 |
| 类型转换错误 | 装饰链中类型判断失误 | 不要依赖具体类型判断 |
10.3 最佳实践
最佳实践清单:
- 接口与实现分离:抽象组件定义规约,具体实现各异
- 抽象装饰器:永远保留一个抽象中间层便于扩展
- 不可变装饰器:尽量让装饰器不可变,提升线程安全
- 提供静态工厂方法:便于用户使用,避免一长串
new Xxx(new Yyy(new Zzz())) - 编写单元测试:对每个装饰器和组合都需要测试
- 文档清晰:每个装饰器的职责与边界要清晰说明
十一、与其他模式的协同
装饰器模式经常与其他模式联合使用:
| 组合模式 | 协同方式 |
|---|---|
| 装饰器 + 工厂 | 用工厂封装装饰链的创建细节 |
| 装饰器 + 建造者 | 用 Builder 灵活构建装饰链 |
| 装饰器 + 策略 | 用策略切换不同装饰算法 |
| 装饰器 + 适配器 | 先适配接口再用装饰器增强 |
十二、总结
12.1 核心要点回顾
装饰器模式的核心要点可以用一句话总结:
在不改变原有接口的前提下,通过层层包装的方式动态叠加新职责。
12.2 知识地图
12.3 适用与不适用场景
12.4 一句话总结
装饰器模式是 Java 程序员必须掌握的核心设计模式之一。它不仅在 JDK 中被大量使用(Java I/O、Collections 工具类等),更是 Spring、MyBatis 等主流框架实现功能扩展的基础。掌握装饰器模式,能够帮助我们写出更灵活、更易维护、更符合开闭原则的代码。
最后更新:2026-10-09
适用读者:Java 开发工程师、设计模式学习者、面向对象架构设计者
配套示例:本文档全部 Java 代码均可直接复制运行(JDK 8 及以上版本)

浙公网安备 33010602011771号