装饰器模式在业务系统中有哪些应用场景?
简化版
装饰器模式适合业务中“基础能力稳定、附加能力可选组合”的场景,例如 HTTP 客户端叠加日志、重试、签名,文件处理叠加压缩、加密,文本处理叠加过滤、脱敏、格式化。它能让每个增强独立维护,并按需组合。
详细版
业务系统中常见装饰器场景包括:
- HTTP 客户端:日志、重试、限流、签名、监控;
- 文件处理:压缩、加密、校验、上传;
- 文本处理:敏感词过滤、脱敏、格式化、统计;
- 消息发送:模板渲染、签名、压缩、埋点;
- 数据源访问:缓存、日志、权限、指标采集;
- UI 组件:边框、阴影、滚动条、拖拽能力。
例如 HTTP 客户端:
HttpClient client = new MetricsHttpClient(
new RetryHttpClient(
new SignHttpClient(
new DefaultHttpClient()
)
)
);
每一层只做一件事:
DefaultHttpClient负责真正发送请求;SignHttpClient负责签名;RetryHttpClient负责失败重试;MetricsHttpClient负责统计耗时和结果。
这样新增一个限流能力,只需要新增 RateLimitHttpClient,不用修改原来的客户端实现。
装饰器尤其适合做 SDK、基础设施组件和可插拔增强链。
完整版教学
一、HTTP 客户端增强链
很多项目都会封装 HTTP 客户端。基础客户端只负责发送请求:
interface HttpClient {
Response execute(Request request);
}
但真实系统里,发送请求前后可能有很多通用逻辑:
- 打日志;
- 添加签名;
- 压缩请求体;
- 失败重试;
- 统计耗时;
- 限流;
- 熔断。
如果全部写进 DefaultHttpClient,这个类会变得很重。
使用装饰器后:
HttpClient client = new LoggingClient(
new RetryClient(
new SigningClient(
new DefaultHttpClient()
)
)
);
每个增强独立,组合顺序也可以集中管理。
二、文件处理链
文件处理也很适合装饰器。
基础能力是读写文件:
interface FileProcessor {
byte[] read();
void write(byte[] data);
}
附加能力可以包括:
- 压缩;
- 加密;
- 校验摘要;
- 记录审计日志;
- 上传到远程存储。
例如写入顺序:
原始数据 -> 压缩 -> 加密 -> 写入
读取顺序:
读取 -> 解密 -> 解压 -> 原始数据
这种前后对称的处理链,很适合用装饰器表达。
三、文本处理和内容安全
文本处理系统经常需要多个可组合步骤:
- 去 HTML;
- 敏感词过滤;
- 手机号脱敏;
- 关键词高亮;
- 统计字数;
- 格式化输出。
可以定义:
interface TextProcessor {
String process(String text);
}
然后组合:
TextProcessor processor = new MaskPhoneDecorator(
new SensitiveWordDecorator(
new PlainTextProcessor()
)
);
如果某个业务只需要脱敏,不需要敏感词过滤,就换一个组合即可。
四、消息发送增强
消息发送也有装饰器空间:
MessageSender sender = new TraceMessageSender(
new TemplateMessageSender(
new DefaultMessageSender()
)
);
每层职责:
- 模板装饰器负责渲染模板;
- Trace 装饰器负责埋点;
- 签名装饰器负责安全校验;
- 默认发送器负责真正发送。
如果不同渠道需要不同增强链,可以用工厂按渠道装配装饰器。
五、什么时候业务里不适合装饰器
如果业务流程本身有强分支、强状态流转,装饰器不一定合适。
例如订单状态从待支付到已支付、已取消、已退款,这种状态流转更适合状态模式、策略模式或领域服务,不适合用装饰器一层层包。
装饰器适合附加能力组合,不适合表达复杂业务主流程。
判断标准是:增强是否可以独立存在、是否围绕同一接口、是否能自由组合。如果答案是肯定,装饰器就比较合适。
六、业务增强是否正交可组合
HTTP 请求依次经过鉴权 2 ms、重试判断 1 ms、日志 1 ms;若首次远程调用 30 ms 成功,总耗时约 34 ms,重试装饰器只在失败时增加后续调用。
base client <- timeout <- retry <- auth <- metrics
这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。
七、边界、代价与验证
| 检查维度 | 应确认的内容 |
|---|---|
| 正确性 | 业务装饰器应一次只增加一种可独立测试的能力,否则很快退化成隐藏的上帝对象。 |
| 适用边界 | 事务主流程、复杂审批和跨步骤补偿通常不是装饰器职责;这些场景需要显式流程和状态。 |
| 测试证据 | 覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序 |
| 运行成本 | 记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量 |
模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。
易错点:业务装饰器应一次只增加一种可独立测试的能力,否则很快退化成隐藏的上帝对象。
八、常见误区与追问
- 误区:任何横切逻辑都适合做装饰器。 只有能围绕同一组件契约独立叠加的职责才合适,强流程依赖不适合。
- 误区:装饰器可以任意改变组件接口。 透明装饰依赖共同的 Component 契约;随意增加只能向下转型调用的方法,会破坏可替换性和继续组合的能力。
- 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
- 追问:业务装饰链由谁装配更稳妥? 由工厂、依赖注入配置或专门组装器集中决定,避免各调用点产生不同顺序。
- 追问:装饰器链越长,扩展性就一定越好吗? 不一定;链越长,顺序、异常传播和排障成本越高,应由装配层集中管理并限制职责粒度。
- 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。
九、加强记忆
业务系统里的装饰器常出现在 HTTP 客户端、文件处理、文本处理、消息发送这些“基础能力 + 多个可选增强”的地方。主流程别装饰,附加能力才装饰。