4. 状态机设计模式:分层状态机、嵌套状态机、历史状态模式在审批流中的应用

在复杂的企业审批流中,单一平面状态机往往难以应对多级审批、子流程回退、以及中断恢复等场景。本章深入剖析三种高级状态机设计模式——分层状态机、嵌套状态机与历史状态模式,并结合审批流中的典型错误处理场景,阐述其设计原理与实现要点。

4.1 分层状态机:职责分离与错误隔离

分层状态机(Hierarchical State Machine, HSM)通过将状态划分为多个层级,实现关注点分离。在审批流中,顶层状态描述单据的生命周期(如“草稿”、“审批中”、“已完成”),而子层状态则描述审批过程中的具体阶段(如“部门审批”、“财务审批”、“总经理审批”)。

核心优势:

  • 错误隔离:子层状态内的异常(如审批人超时未处理)不会影响顶层状态机的稳定性。
  • 复用性:通用的“审批中”顶层状态可被多个子层状态机共享。
  • 可读性:状态转换图更清晰,避免状态爆炸。
设计要点: 子层状态机在进入时,需向父层状态机注册其当前状态;父层状态机仅在子层状态机报告“最终完成”或“异常终止”时,才进行顶层状态迁移。
分层状态机在审批流中的应用 父层状态机:单据生命周期 草稿 审批中 已完成 子层状态机:审批中 部门审批 财务审批 总经理审批 驳回回退 完成通知

4.2 嵌套状态机:子流程的独立生命周期

嵌套状态机(Nested State Machine)与分层状态机类似,但更强调子状态机的独立性。在审批流中,一个“会签”环节本身就是一个嵌套状态机:它内部包含多个“待签收”、“已签收”、“已拒绝”等状态,且拥有独立的超时与错误处理逻辑。

典型应用场景:

  • 会签/并签:需要多个审批人独立审批,每个审批人拥有自己的状态机实例。
  • 条件分支:根据金额或部门不同,进入不同的嵌套审批子流程。
  • 子流程回退:嵌套状态机内部支持“退回修改”状态,不影响主流程状态。
特性 分层状态机 嵌套状态机
状态归属 子状态是父状态的一部分 子状态机拥有独立的状态空间
错误处理 子层异常可冒泡到父层 子状态机内部处理异常,仅通知父层结果
生命周期 子层随父层创建/销毁 子状态机可独立创建和销毁
典型场景 多级审批阶段 会签、子流程、并行审批
实现建议: 嵌套状态机推荐使用独立的上下文(Context)对象存储子状态机实例。主状态机通过事件(Event)驱动子状态机执行,子状态机通过回调(Callback)返回结果。

4.3 历史状态模式:中断恢复与断点续审

历史状态模式(History State Pattern)用于记录状态机进入某个复合状态前的子状态,以便在重新进入时恢复到上次离开的位置。在审批流中,当审批被“驳回”或“撤回”后,再次提交时应当恢复到之前的审批阶段,而非从头开始。

两种历史类型:

  • 浅历史(Shallow History):仅记录当前层级的子状态,不记录更深层嵌套状态。
  • 深历史(Deep History):递归记录所有嵌套层级的子状态,实现完全恢复。
历史状态模式在审批流中的应用 历史状态模式:审批中断恢复 提交申请 部门审批 财务审批 审批通过 驳回(记录历史状态) 历史状态存储(History State) 记录:当前审批阶段 = “财务审批” 记录:审批人 = “张三”,已处理 = true 重新提交:恢复历史状态

4.4 三种模式的组合应用与错误处理策略

在实际企业审批流中,这三种模式往往组合使用,形成强大的错误处理骨架:

  1. 顶层使用分层状态机管理单据生命周期,确保“审批中”状态下的任何子层异常都不会导致单据状态丢失。
  2. 会签环节使用嵌套状态机,每个审批人拥有独立状态,超时或拒绝仅影响该子状态机实例。
  3. 驳回场景使用历史状态模式,记录当前审批阶段和已处理节点,重新提交时精准恢复。
常见陷阱: 历史状态模式在嵌套状态机中应用时,需注意深历史可能带来的状态膨胀问题。建议对历史状态设置最大深度限制(如最多记录3层嵌套),超出部分视为“重新开始”。

4.5 代码示例:历史状态模式在审批流中的实现

以下代码展示了一个简化的历史状态模式实现,用于审批流中断恢复:

// 历史状态管理器
public class HistoryStateManager {
    private final Map<String, State> historyStore = new ConcurrentHashMap<>();
    
    // 保存当前状态快照
    public void saveSnapshot(String processId, State currentState) {
        historyStore.put(processId, currentState.deepCopy());
    }
    
    // 恢复历史状态
    public State restoreState(String processId) {
        State state = historyStore.get(processId);
        if (state == null) {
            return new InitialState(); // 无历史则从头开始
        }
        return state;
    }
    
    // 清除历史(审批最终完成或取消时调用)
    public void clearHistory(String processId) {
        historyStore.remove(processId);
    }
}

// 审批流状态机中使用历史状态
public class ApprovalStateMachine {
    private HistoryStateManager historyManager = new HistoryStateManager();
    
    public void onRejected(String processId, State currentState) {
        // 驳回时保存历史
        historyManager.saveSnapshot(processId, currentState);
        transitionTo(new RejectedState());
    }
    
    public void onResubmit(String processId) {
        // 重新提交时恢复历史
        State restoredState = historyManager.restoreState(processId);
        transitionTo(restoredState);
    }
}
最佳实践: 历史状态数据建议持久化到数据库或Redis,而非仅存于内存。这样即使服务重启,审批流也能从断点处恢复,避免业务数据丢失。

通过合理运用分层状态机、嵌套状态机与历史状态模式,企业审批流系统能够优雅地处理超时、驳回、撤回、并发冲突等复杂错误场景,构建出健壮、可维护的业务骨架。