装饰模式:动态扩展对象功能的优雅设计
一、模式核心:不修改原类,动态添加功能
在软件开发中,我们经常需要为现有对象添加新功能(如给 "文本框" 添加 "验证功能""日志功能"),但直接修改原类会违反开闭原则。装饰模式(Decorator Pattern) 通过创建一个包装对象(装饰器),在不改变原对象的前提下动态扩展其功能,核心解决:
- 功能动态组合:可灵活选择不同装饰器组合,实现功能的 "即插即用"
- 避免继承膨胀:替代多层继承,减少子类数量(如 "带验证的文本框" 无需继承原文本框类)
核心思想与 UML 类图
装饰模式通过让装饰器与被装饰对象实现相同接口,形成递归包装结构,使客户端透明地使用装饰后的对象:

二、核心实现:为文件流添加加密与压缩功能
1. 定义组件接口(抽象功能)
java
public interface DataStream {
void write(String data); // 写入数据
}
2. 实现具体组件(基础功能:普通文件流)
java
public class FileStream implements DataStream {
private String filePath;
public FileStream(String filePath) {
this.filePath = filePath;
}
@Override
public void write(String data) {
System.out.println("写入文件 [" + filePath + "]: " + data);
}
}
3. 定义装饰器抽象类(维护被装饰对象引用)
java
public abstract class StreamDecorator implements DataStream {
protected DataStream dataStream;
public StreamDecorator(DataStream dataStream) {
this.dataStream = dataStream;
}
@Override
public void write(String data) {
dataStream.write(data); // 委托给被装饰对象
}
}
4. 实现具体装饰器(新增加密功能)
java
public class EncryptionDecorator extends StreamDecorator {
public EncryptionDecorator(DataStream dataStream) {
super(dataStream);
}
// 加密算法(示例:简单反转字符串)
private String encrypt(String data) {
return new StringBuilder(data).reverse().toString();
}
@Override
public void write(String data) {
String encryptedData = encrypt(data); // 前置加密处理
super.write(encryptedData); // 调用被装饰对象的写入逻辑
}
}
5. 实现另一个装饰器(新增压缩功能)
java
public class CompressionDecorator extends StreamDecorator {
public CompressionDecorator(DataStream dataStream) {
super(dataStream);
}
// 压缩算法(示例:简单字符串截断)
private String compress(String data) {
return data.length() > 10 ? data.substring(0, 10) + "..." : data;
}
@Override
public void write(String data) {
String compressedData = compress(data); // 前置压缩处理
super.write(compressedData);
}
}
6. 客户端组合装饰器(动态扩展功能)
java
public class ClientDemo {
public static void main(String[] args) {
// 基础功能:普通文件流
DataStream simpleStream = new FileStream("data.txt");
simpleStream.write("Hello, World!"); // 直接写入原始数据
// 组合加密装饰器
DataStream encryptedStream = new EncryptionDecorator(simpleStream);
encryptedStream.write("Secret Message"); // 写入前自动加密
// 组合加密+压缩装饰器(顺序影响结果)
DataStream complexStream = new CompressionDecorator(
new EncryptionDecorator(simpleStream)
);
complexStream.write("LongTextThatNeedsCompressionAndEncryption");
}
}
三、进阶:实现 JDK IO 式的多层装饰器链
1. 支持链式调用的装饰器设计
java
public abstract class ChainableDecorator implements DataStream {
protected DataStream dataStream;
public ChainableDecorator(DataStream dataStream) {
this.dataStream = dataStream;
}
// 支持连续装饰(返回当前装饰器实例)
public DataStream decorate(DataStream decorator) {
return new ChainableDecorator(decorator) {
@Override
public void write(String data) {
// 可自定义装饰顺序(前置/后置)
data = "[" + data + "]"; // 新增包装逻辑
super.write(data);
}
};
}
}
// 使用示例:链式装饰
DataStream stream = new FileStream("log.txt");
stream = new EncryptionDecorator(stream).decorate(new CompressionDecorator(stream));
2. 装饰器与 AOP 的结合(方法级增强)
java
// 使用Spring AOP实现装饰器逻辑
@Aspect
public class StreamAspect {
@Around("execution(* com.example.DataStream.write(..))")
public Object addDecorator(ProceedingJoinPoint joinPoint) throws Throwable {
String data = (String) joinPoint.getArgs()[0];
// 前置增强:加密
String encryptedData = encrypt(data);
// 调用原始方法
joinPoint.proceed(new Object[]{encryptedData});
// 后置增强:记录日志
logWrite(encryptedData);
return null;
}
}
3. 可视化装饰流程(Mermaid 流程图)
graph LR
A[客户端请求] --> B[装饰器A处理]
B --> C[被装饰对象处理]
C --> D[装饰器B处理]
D --> E[返回结果]
四、框架与源码中的装饰模式实践
1. Java IO 流(典型装饰模式应用)
-
核心类:
- 抽象组件:
InputStream
/OutputStream
- 具体组件:
FileInputStream
/FileOutputStream
- 装饰器:
BufferedInputStream
(添加缓冲)、DataInputStream
(添加数据类型支持)
- 抽象组件:
-
使用示例:
javaInputStream input = new BufferedInputStream( new DataInputStream( new FileInputStream("file.txt") ) );
2. Servlet Filter(请求响应装饰)
Filter
通过FilterChain
形成装饰链,对请求和响应进行增强
java
public class EncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
request = new EncodedServletRequest((HttpServletRequest) request); // 装饰请求
chain.doFilter(request, response); // 传递给下一个Filter
}
}
3. MyBatis 插件(Interceptor)
- 通过拦截器装饰
Executor
、StatementHandler
等对象,添加分页、性能监控等功能
java
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class PerformanceInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed(); // 调用原始方法
long end = System.currentTimeMillis();
logPerformance(end - start); // 后置装饰:记录执行时间
return result;
}
}
五、避坑指南:正确使用装饰模式的 3 个要点
1. 确保装饰器与组件接口一致
- ❌ 反模式:装饰器未实现与组件相同的接口,导致客户端无法透明调用
- ✅ 最佳实践:装饰器必须继承 / 实现组件接口,并重写所有方法
2. 控制装饰链长度(避免性能损耗)
- 过长的装饰链会增加方法调用层级,建议:
- 对装饰器进行分组(如 "输入处理组""输出处理组")
- 使用
CompositeDecorator
合并同类装饰器
3. 避免装饰顺序影响逻辑
- 装饰器的执行顺序可能影响结果(如先加密后压缩 vs 先压缩后加密),需:
- 在文档中明确装饰器顺序规范
- 提供工厂类统一管理装饰链组装
4. 反模式:滥用装饰器替代继承
- 当功能扩展是 "is-a" 关系时(如 "鸟类" 是 "动物类"),应使用继承而非装饰器
六、总结:何时该用装饰模式?
适用场景 | 核心特征 | 典型案例 |
---|---|---|
动态添加功能 | 功能可灵活组合,且运行时动态决定 | GUI 组件皮肤切换、游戏角色技能升级 |
避免类爆炸 | 功能组合可能导致子类数量激增(如 n 个功能→2ⁿ个子类) | 日志系统(DEBUG/INFO/ERROR 级别组合) |
遵循开闭原则 | 不修改原代码,通过新增装饰器扩展功能 | 框架插件机制、遗留系统功能增强 |
装饰模式通过 "包装对象 + 功能委托" 的设计,使系统在不破坏原有结构的前提下实现功能的动态扩展,是应对 "需求变化" 的核心设计模式之一。下一篇我们将深入探讨适配器模式,解析如何让不兼容的接口协同工作,敬请期待!
扩展思考:装饰模式 vs 代理模式
两者都通过包装对象实现功能增强,但核心差异在于:
模式 | 目标 | 持有对象关系 | 典型场景 |
---|---|---|---|
装饰模式 | 增强功能(附加职责) | 装饰器与组件是 "is-a" 关系 | 添加日志、加密、压缩 |
代理模式 | 控制访问(替代职责) | 代理与目标是 "proxy-for" 关系 | 远程调用、权限控制、懒加载 |
理解这种差异,能帮助我们在设计时选择更合适的模式来解决实际问题。