问题从哪里开始

项目里集成了 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)) {
        // 获取信息命令逻辑
    }
    // 更多分支...
}

问题来了:

  1. 代码冗余:每种处理逻辑散落在 if-else 分支里,提取方法也救不了结构混乱
  2. 扩展性差:新增一种回调类型?再加一个 else if,然后祈祷别改坏其他分支
  3. 可读性低:几百行的方法,看完上面忘了下面,新人接手直接懵
  4. 测试麻烦:想测 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 {
    // ...
}

好处:

  1. 不用手动注册:新增策略只需加 @Component
  2. 依赖注入友好:策略类可以注入其他 Bean
  3. 配置更灵活:可以用 @Order 控制策略优先级

什么时候用策略模式?

回顾这次重构,策略模式适合:

  1. 多分支选择:代码里有一长串 if-else / switch
  2. 分支独立:各分支逻辑互不干扰
  3. 扩展频繁:未来可能新增分支类型
  4. 类型标识明确:有字段可以区分不同类型(如我们的 status

像 OnlyOffice 回调这种报文驱动的场景,策略模式几乎是标配。类似的还有:

  • 消息队列消费者(不同消息类型)
  • HTTP 请求路由(不同 API)
  • 事件监听分发
  • 支付渠道选择

写在最后

设计模式不是炫技,是解决实际问题的工具。当 if-else 开始"野蛮生长",就该想想是不是策略模式入场的时候了。

重构之后,每次新增回调类型,我只需要:

  1. 新建一个 Handler 类
  2. 实现两个方法
  3. 加个 @Component

完事。不用碰 Service,不用改工厂,不用担心改坏其他逻辑。