问题从哪里开始
项目里集成了 OnlyOffice 文档编辑器,功能挺好用,但回调处理这块慢慢变成了"噩梦"。
OnlyOffice 会推送各种回调,比如用户保存文档、强制保存、文档关闭等等。每种回调有不同的 status 字段,对应不同的处理逻辑。一开始代码写得很"朴素":
// FileServiceImpl.java - 曾经的模样
public void onlyOfficeCallback(OnlyOfficeCallbackDTO callbackDTO, File file) {
if (callbackDTO.getStatus() == 2) {
if (callbackDTO.getActions() != null
&& callbackDTO.getActions().stream().anyMatch(a -> a.getType() == 0)) {
// 保存逻辑:下载文档、更新文件...
// 几十行代码
}
} else if (callbackDTO.getStatus() == 6) {
// 强制保存逻辑
// 又是几十行代码
} else if (callbackDTO.getStatus() == 1) {
// 正在编辑...
} else if (...) {
// 更多分支...
}
}
然后还有 Command 调用——主动向 OnlyOffice 发命令的逻辑,也堆在同一个 Service 里:
public void sendCommand(String command, Map<String, Object> params) {
if ("forcesave".equals(command)) {
// 强制保存命令逻辑
} else if ("info".equals(command)) {
// 获取信息命令逻辑
}
// 更多分支...
}
问题来了:
- 代码冗余:每种处理逻辑散落在 if-else 分支里,提取方法也救不了结构混乱
- 扩展性差:新增一种回调类型?再加一个
else if,然后祈祷别改坏其他分支 - 可读性低:几百行的方法,看完上面忘了下面,新人接手直接懵
- 测试麻烦:想测
status=6的逻辑?得 mock 整个 Service,还要保证其他分支不会出问题
这就是典型的"策略模式适用的场景"——多种算法/行为需要在运行时选择。
为什么是策略模式?
GoF 对策略模式的定义:
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。
这正好对应我们的问题:
| 现状 | 策略模式的解法 |
|---|---|
| 每种回调一个 if 分支 | 每种回调一个策略类 |
| 分支判断逻辑硬编码 | 策略自带判断能力 |
| 新增回调改 Service | 新增策略类 + 注册 |
| 测试要 mock 整体 | 单独测策略类 |
更妙的是,OnlyOffice 的回调报文有明确的类型标识(status 字段),天然适合做策略分发。
重构实践
第一步:定义策略接口
核心洞察:每个策略需要两个能力——判断能不能处理和具体处理逻辑。
// CallbackHandler.java
public interface CallbackHandler {
/**
* 判断当前策略是否适合处理这个回调
*/
boolean shouldHandle(OnlyOfficeCallbackDTO callbackDTO);
/**
* 执行处理逻辑
*/
void handle(OnlyOfficeCallbackDTO callbackDTO, File.OnlyOfficeEditFile file);
}
为什么要 shouldHandle() 而不是让工厂判断?因为策略自己最清楚自己能处理什么。把判断逻辑内聚到策略里,工厂就变得很纯粹——遍历、匹配、返回。
// CommandHandler.java - 命令处理也如法炮制
public interface CommandHandler {
boolean shouldHandle(Map<String, Object> params);
void handle(Map<String, Object> params);
}
第二步:实现具体策略
// SaveHandler.java - 处理 status=2(文档保存完成)
public class SaveHandler implements CallbackHandler {
@Override
public boolean shouldHandle(OnlyOfficeCallbackDTO callbackDTO) {
// status=2 且有退出编辑动作
return callbackDTO != null
&& callbackDTO.getStatus() == 2
&& callbackDTO.getActions() != null
&& callbackDTO.getActions().stream()
.anyMatch(action -> action.getType() == 0);
}
@Override
public void handle(OnlyOfficeCallbackDTO callbackDTO, File.OnlyOfficeEditFile file) {
// 清爽的处理逻辑,不用管其他分支
// 下载文档、更新文件信息...
}
}
// ForceSaveHandler.java - 处理 status=6(强制保存)
public class ForceSaveHandler implements CallbackHandler {
@Override
public boolean shouldHandle(OnlyOfficeCallbackDTO callbackDTO) {
return callbackDTO != null && callbackDTO.getStatus() == 6;
}
@Override
public void handle(OnlyOfficeCallbackDTO callbackDTO, File.OnlyOfficeEditFile file) {
// 强制保存的处理逻辑
}
}
每个策略类职责单一,代码独立,测试时只需测这个类。
第三步:工厂模式管理策略
策略有了,谁来选?工厂出场:
public class CallbackHandlerFactory {
private static final List<CallbackHandler> handlers = new ArrayList<>();
static {
// 注册所有策略
handlers.add(new SaveHandler());
handlers.add(new ForceSaveHandler());
// 新增策略?加一行就行
}
public static CallbackHandler getHandler(OnlyOfficeCallbackDTO callbackDTO) {
for (CallbackHandler handler : handlers) {
if (handler.shouldHandle(callbackDTO)) {
return handler;
}
}
return null; // 没有匹配的策略
}
}
工厂的逻辑极其简单:遍历所有策略,找到第一个说"我来处理"的。
第四步:客户端调用
Service 层终于解脱了:
// FileServiceImpl.java - 重构后
@Override
public void onlyOfficeCallback(OnlyOfficeCallbackDTO callbackDTO, File.OnlyOfficeEditFile file) {
CallbackHandler handler = CallbackHandlerFactory.getHandler(callbackDTO);
if (handler != null) {
handler.handle(callbackDTO, file);
} else {
log.info("未处理的回调类型: {}", callbackDTO);
}
}
三行代码,干干净净。
架构一览
┌─────────────────────────────────────────────────────────────┐
│ FileServiceImpl │
│ (客户端调用者) │
└──────────────────────┬──────────────────────────────────────┘
│ getHandler(callbackDTO)
▼
┌─────────────────────────────────────────────────────────────┐
│ CallbackHandlerFactory │
│ │
│ handlers: [SaveHandler, ForceSaveHandler, ...] │
│ │
│ getHandler() → 遍历 → shouldHandle()? → return │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ CallbackHandler (Strategy 接口) │
│ │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ SaveHandler │ │ ForceSaveHandler │ │
│ │ status=2 │ │ status=6 │ │
│ └──────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
收益
重构完成后:
| 维度 | 重构前 | 重构后 |
|---|---|---|
| 新增回调类型 | 改 Service,加 if-else | 新建策略类,工厂注册 |
| 单元测试 | mock 整个 Service | 独立测策略类 |
| 代码可读性 | 300 行大方法 | 每个策略 30-50 行 |
| 职责划分 | Service 承载所有逻辑 | 策略各司其职 |
开闭原则在这里体现得淋漓尽致——新增功能不改动现有代码,只增加新类。
进阶:更 Spring 的写法
上面的实现用静态注册,其实可以用 Spring 的自动注入更优雅:
@Component
public class CallbackHandlerFactory {
private final List<CallbackHandler> handlers;
// Spring 自动注入所有 CallbackHandler Bean
public CallbackHandlerFactory(List<CallbackHandler> handlers) {
this.handlers = handlers;
}
public CallbackHandler getHandler(OnlyOfficeCallbackDTO dto) {
return handlers.stream()
.filter(h -> h.shouldHandle(dto))
.findFirst()
.orElse(null);
}
}
// 每个策略加 @Component,Spring 自动扫描注册
@Component
public class SaveHandler implements CallbackHandler {
// ...
}
好处:
- 不用手动注册:新增策略只需加
@Component - 依赖注入友好:策略类可以注入其他 Bean
- 配置更灵活:可以用
@Order控制策略优先级
什么时候用策略模式?
回顾这次重构,策略模式适合:
- 多分支选择:代码里有一长串 if-else / switch
- 分支独立:各分支逻辑互不干扰
- 扩展频繁:未来可能新增分支类型
- 类型标识明确:有字段可以区分不同类型(如我们的
status)
像 OnlyOffice 回调这种报文驱动的场景,策略模式几乎是标配。类似的还有:
- 消息队列消费者(不同消息类型)
- HTTP 请求路由(不同 API)
- 事件监听分发
- 支付渠道选择
写在最后
设计模式不是炫技,是解决实际问题的工具。当 if-else 开始"野蛮生长",就该想想是不是策略模式入场的时候了。
重构之后,每次新增回调类型,我只需要:
- 新建一个 Handler 类
- 实现两个方法
- 加个
@Component
完事。不用碰 Service,不用改工厂,不用担心改坏其他逻辑。