文章目录
-
- 前言
- [1. 装饰器模式是什么?](#1. 装饰器模式是什么?)
- [2. 装饰器模式解决什么问题?](#2. 装饰器模式解决什么问题?)
- [3. 实现步骤](#3. 实现步骤)
- [4. 静态结构](#4. 静态结构)
-
- [4.1 抽象组件:Coffee(统一接口)](#4.1 抽象组件:Coffee(统一接口))
- [4.2 具体组件:SimpleCoffee(基础咖啡)](#4.2 具体组件:SimpleCoffee(基础咖啡))
- [4.3 装饰器抽象类:CoffeeDecorator](#4.3 装饰器抽象类:CoffeeDecorator)
- [4.4 具体装饰器:Milk / Sugar](#4.4 具体装饰器:Milk / Sugar)
- [5. 动态结构 / 运行时替换点](#5. 动态结构 / 运行时替换点)
- [6. 优缺点](#6. 优缺点)
-
- [6.1 优点](#6.1 优点)
- [6.2 缺点](#6.2 缺点)
- [7. 和模板方法模式对比](#7. 和模板方法模式对比)
-
- [7.1 都能"增强行为",但侧重点不同](#7.1 都能“增强行为”,但侧重点不同)
- [7.2 谁来控制流程?](#7.2 谁来控制流程?)
- [8. 和代理模式或外观模式的区别](#8. 和代理模式或外观模式的区别)
-
- [8.1 装饰器 vs 代理](#8.1 装饰器 vs 代理)
- [8.2 装饰器 vs 外观](#8.2 装饰器 vs 外观)
- [9. 总结](#9. 总结)
前言
在面向对象设计里,我们经常遇到这样的需求:
给一个对象"加功能",而且不想改它的类,也不想用继承把功能层层叠叠地搞爆。
这时候,装饰器模式(Decorator Pattern) 就很合适。
可以把它理解成:在不改变原对象代码的前提下,用"包装一层层"的方式给对象动态叠加职责。

1. 装饰器模式是什么?
**装饰器模式:**在不改变原对象结构的情况下,通过创建一个包装类(Decorator),在运行时给对象增加额外功能。
它的核心角色一般是:
- Component(抽象组件):定义对象的公共接口(业务能力)
- ConcreteComponent(具体组件):真正的被装饰对象
- Decorator(装饰器抽象类/基类) :持有一个
Component,并实现接口;用于"转发 + 增强" - ConcreteDecorator(具体装饰器):对某个/某类功能进行增强(例如加日志、加缓存、加权限)
2. 装饰器模式解决什么问题?
典型场景是:你想给对象增加功能,但功能组合有很多种,且希望可灵活启用/禁用。
常见例子:
-
UI 控件增强
- 文本框 + 边框
- 文本框 + 边框 + 水印
- 文本框 + 边框 + 校验 + 错误提示...
-
请求/响应增强
- 统一日志
- 统一鉴权
- 统一缓存
- 统一压缩/序列化...
-
流式处理
- 输入流 + 解密
- 输出流 + 编码
- 输入流 + 校验 + 解密...
-
电商价格/计费规则拼装
- 基础价格 + 满减 + 优惠券 + 会员价...
3. 实现步骤
-
先抽象出公共接口
Component- 明确"对象能做什么"(核心方法签名)
-
写一个或多个
ConcreteComponent- 提供基础能力(被装饰者)
-
写装饰器基类
Decorator- 内部持有一个
Component - 大多数情况下,先把调用转发给被包装对象
- 内部持有一个
-
写一个或多个
ConcreteDecorator- 在转发前/后做增强
- 也可以选择不转发、改变结果(更灵活)
-
客户端按需"层层包裹"
- 这一步带来装饰器模式的"动态叠加"能力
4. 静态结构
用一个"咖啡加料"例子来展示装饰器模式的静态结构与调用关系。
4.1 抽象组件:Coffee(统一接口)
java
public interface Coffee {
String make();
}
4.2 具体组件:SimpleCoffee(基础咖啡)
java
public class SimpleCoffee implements Coffee {
@Override
public String make() {
return "基础咖啡";
}
}
4.3 装饰器抽象类:CoffeeDecorator
java
public abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee; // 持有被装饰对象
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String make() {
return coffee.make(); // 默认转发
}
}
4.4 具体装饰器:Milk / Sugar
java
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String make() {
return coffee.make() + " + 加奶";
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String make() {
return coffee.make() + " + 加糖";
}
}
5. 动态结构 / 运行时替换点
装饰器模式真正"动态"的地方在于:
运行时决定包裹哪些装饰器,以及包裹顺序。
客户端示例:
java
public class Client {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee); // 动态叠加:加奶
coffee = new SugarDecorator(coffee); // 动态叠加:再加糖
System.out.println(coffee.make());
// 输出:基础咖啡 + 加奶 + 加糖
}
}
你会发现:
SimpleCoffee不需要改MilkDecorator、SugarDecorator都只关心"自己要增加的部分"- 组合方式由客户端灵活决定
6. 优缺点
6.1 优点
- 比继承更灵活:不必为了每个组合创建大量子类
- 可组合、可动态启用/禁用:运行时自由叠加职责
- 符合开闭原则:新增功能通常只加新的装饰器类,不改原类
6.2 缺点
- 装饰层级可能过多:组合复杂时,调用链会变长
- 调试/排查问题更复杂:因为行为来自一串包装层
- 过度使用会让结构"绕":简单场景可能不需要装饰器
7. 和模板方法模式对比
7.1 都能"增强行为",但侧重点不同
- 模板方法(Template Method) :强调"固定流程骨架 + 子类替换步骤"
- 装饰器(Decorator) :强调"对象被包装 + 运行时叠加职责"
可以这样记忆:
模板方法:改的是"流程里的某些步骤(继承替换)"
装饰器:改的是"对象外面加了什么功能(包装叠加)"
7.2 谁来控制流程?
- 模板方法:父类控制流程顺序(骨架算法)
- 装饰器:调用沿着包装链传递(谁在外层谁先/后增强,顺序可控)
8. 和代理模式或外观模式的区别
8.1 装饰器 vs 代理
两者都"包装一个对象",但意图不同:
- 代理模式 :核心目标通常是 控制访问/管理远程/延迟加载/权限
(比如:懒加载真实对象、权限校验、记录谁访问了它) - 装饰器模式 :核心目标是 增强功能/增加职责
(比如:日志、加糖加奶、加校验、加缓存等"附加行为")
简单判断:
你包装它是为了"替你去做访问控制/转发到真实对象" → 更像代理
你包装它是为了"在同一对象能力上继续加功能" → 更像装饰器
8.2 装饰器 vs 外观
- 外观模式:把复杂系统封装成一个简单入口("统一门面")
- 装饰器模式:多层可组合地给单个对象"叠加职责"
9. 总结
装饰器模式的核心可以概括为一句话:
用包装(Decorator)在不修改原对象的前提下,运行时动态叠加额外职责。
它适合:
- 功能可组合且组合种类多
- 希望减少继承导致的爆炸
- 希望运行时决定增强哪些能力