设计模式与工作流引擎:状态模式管理流程状态、责任链处理任务、模板方法定义流程

工作流引擎,说白了就是一套自动化的流程调度系统。你提交一个请假申请,它自动走审批、抄送、归档——这背后就是状态流转和任务处理。我这些年做过几个工作流相关的项目,从简单的审批流到复杂的工单系统,发现设计模式在这里简直是天作之合。

今天咱们就聊聊三个核心模式:状态模式管流程状态、责任链模式处理任务、模板方法模式定义流程骨架。这三个模式配合起来,能写出非常优雅的工作流引擎。

为什么是这三个模式?

工作流引擎的核心问题有三个:

  • 状态怎么变?——每个节点都有前置状态和后置状态,状态迁移逻辑容易写成一团乱麻。
  • 任务谁处理?——审批、通知、日志、校验……一堆任务要按顺序执行,还可能动态增减。
  • 流程骨架怎么定?——不同业务(请假、报销、采购)流程不同,但步骤结构相似。

状态模式解决第一个问题,责任链解决第二个,模板方法解决第三个。我习惯把它们叫做「工作流三剑客」。

核心思路:状态模式负责「当前在哪」和「下一步能去哪」;责任链负责「到了这一步要做什么」;模板方法负责「整个流程长什么样」。

状态模式:让状态迁移变得清晰

先看状态模式。工作流里的每个节点,本质上就是一个状态。比如请假流程:草稿 → 部门审批 → 人事审批 → 结束。每个状态都知道自己能转移到哪些状态。

我见过很多新手把状态迁移写成 if-else 大法:

// 反面教材
if (currentState == "草稿" && action == "提交") {
    currentState = "部门审批";
} else if (currentState == "部门审批" && action == "通过") {
    currentState = "人事审批";
} else if (...) { ... }

这种代码,加一个新状态就要改一大片。我曾经在一个项目里接手过这样的代码,光状态迁移逻辑就写了 300 行,看得我头皮发麻。

用状态模式重构后,每个状态自己管自己的迁移:

// 状态接口
public interface WorkflowState {
    String getStateName();
    WorkflowState next(String action);
    List<String> getAvailableActions();
}

// 具体状态:草稿
public class DraftState implements WorkflowState {
    @Override
    public String getStateName() { return "草稿"; }

    @Override
    public WorkflowState next(String action) {
        if ("提交".equals(action)) {
            return new DeptApprovalState();
        }
        throw new IllegalStateException("草稿状态不支持操作: " + action);
    }

    @Override
    public List<String> getAvailableActions() {
        return Arrays.asList("提交", "删除");
    }
}

// 具体状态:部门审批
public class DeptApprovalState implements WorkflowState {
    @Override
    public String getStateName() { return "部门审批"; }

    @Override
    public WorkflowState next(String action) {
        if ("通过".equals(action)) {
            return new HrApprovalState();
        } else if ("驳回".equals(action)) {
            return new DraftState();
        }
        throw new IllegalStateException("部门审批状态不支持操作: " + action);
    }

    @Override
    public List<String> getAvailableActions() {
        return Arrays.asList("通过", "驳回");
    }
}

你看,每个状态只关心自己能做什么、能去哪。加一个新状态,比如「总经理审批」,只需要新增一个类,实现接口,然后在对应的状态里加上迁移逻辑就行。完全符合开闭原则。

我的习惯:状态对象里不要存业务数据。状态只负责「迁移规则」,数据放在工作流上下文里。这样状态对象可以复用,不会因为数据不同而创建多个实例。

责任链模式:任务处理流水线

状态迁移搞定了,接下来是「到了这个状态要做什么」。比如进入「部门审批」状态后,需要:记录日志 → 发送通知 → 执行审批逻辑 → 更新数据库。这些任务有顺序,还可能动态调整。

责任链模式正好干这个。每个处理器只做一件事,然后交给下一个:

// 处理器接口
public interface TaskHandler {
    void handle(TaskContext context);
    TaskHandler setNext(TaskHandler next);
}

// 抽象基类
public abstract class AbstractTaskHandler implements TaskHandler {
    private TaskHandler next;

    @Override
    public void handle(TaskContext context) {
        doHandle(context);
        if (next != null) {
            next.handle(context);
        }
    }

    protected abstract void doHandle(TaskContext context);

    @Override
    public TaskHandler setNext(TaskHandler next) {
        this.next = next;
        return next;
    }
}

// 具体处理器:日志记录
public class LogHandler extends AbstractTaskHandler {
    @Override
    protected void doHandle(TaskContext context) {
        System.out.println("记录日志: 状态变更为 " + context.getCurrentState());
    }
}

// 具体处理器:通知发送
public class NotificationHandler extends AbstractTaskHandler {
    @Override
    protected void doHandle(TaskContext context) {
        System.out.println("发送通知给 " + context.getAssignee());
    }
}

// 具体处理器:数据库更新
public class DatabaseHandler extends AbstractTaskHandler {
    @Override
    protected void doHandle(TaskContext context) {
        System.out.println("更新数据库: 流程ID=" + context.getProcessId());
    }
}

组装起来也很直观:

TaskHandler chain = new LogHandler();
chain.setNext(new NotificationHandler())
     .setNext(new DatabaseHandler());

// 执行整条链
chain.handle(context);

想加一个「短信提醒」?写个 SmsHandler,插到链里就行。想调整顺序?改一下组装代码。我曾经在一个项目中,用责任链把 20 多个乱七八糟的任务整理得清清楚楚,每个处理器不到 50 行代码。

注意:责任链里的处理器不要共享可变状态。每个处理器应该只依赖 TaskContext 里的数据,不要互相传参。否则链条一长,数据流就乱了。

模板方法模式:定义流程骨架

现在状态迁移和任务处理都有了,但不同业务的流程结构不同。比如请假流程是「提交→审批→结束」,报销流程是「提交→财务审核→主管审批→结束」。虽然步骤不同,但骨架相似:创建 → 流转 → 结束

模板方法模式把骨架定好,具体步骤留给子类实现:

public abstract class WorkflowTemplate {

    // 模板方法:定义流程骨架
    public final void execute(WorkflowContext context) {
        // 1. 初始化
        init(context);

        // 2. 循环处理状态迁移
        while (!isFinished(context)) {
            // 获取当前状态
            WorkflowState currentState = context.getCurrentState();

            // 3. 执行当前状态的任务链
            TaskHandler handler = createTaskChain(context);
            handler.handle(context);

            // 4. 执行状态迁移
            String action = getNextAction(context);
            WorkflowState nextState = currentState.next(action);
            context.setCurrentState(nextState);

            // 5. 状态变更后的回调
            onStateChanged(context, currentState, nextState);
        }

        // 6. 流程结束
        onFinished(context);
    }

    // 子类可重写的方法
    protected void init(WorkflowContext context) {}
    protected void onStateChanged(WorkflowContext context, WorkflowState oldState, WorkflowState newState) {}
    protected void onFinished(WorkflowContext context) {}

    // 子类必须实现的方法
    protected abstract boolean isFinished(WorkflowContext context);
    protected abstract TaskHandler createTaskChain(WorkflowContext context);
    protected abstract String getNextAction(WorkflowContext context);
}

具体业务只需要继承这个模板:

public class LeaveWorkflow extends WorkflowTemplate {
    @Override
    protected boolean isFinished(WorkflowContext context) {
        return context.getCurrentState() instanceof EndState;
    }

    @Override
    protected TaskHandler createTaskChain(WorkflowContext context) {
        TaskHandler chain = new LogHandler();
        chain.setNext(new NotificationHandler())
             .setNext(new DatabaseHandler());
        return chain;
    }

    @Override
    protected String getNextAction(WorkflowContext context) {
        // 实际项目中从用户输入或策略中获取
        return "通过";
    }

    @Override
    protected void onFinished(WorkflowContext context) {
        System.out.println("请假流程结束,归档处理");
    }
}

模板方法把「不变的部分」锁死,把「变的部分」开放出去。我习惯把模板方法写成 final,防止子类不小心改乱了流程骨架。

三者如何配合?

这三个模式不是孤立的,它们在工作流引擎里各司其职:

  • 模板方法是导演,定好「先做什么、再做什么」。
  • 状态模式是剧本,告诉导演「当前在哪、下一步能去哪」。
  • 责任链是演员,每个演员只演好自己的戏份。

我画了一张图,帮你理解它们的关系:

工作流引擎:三种设计模式协作关系 模板方法模式(流程骨架) 状态模式(状态迁移) 草稿 部门审批 人事审批 结束 责任链模式(任务处理) 日志记录 通知发送 数据库更新 调用 模板方法控制整体流程,状态模式管理状态迁移,责任链处理每个状态下的具体任务

实际运行时,流程是这样的:

  1. 模板方法的 execute() 启动流程。
  2. 获取当前状态(比如「草稿」),调用责任链处理该状态下的任务。
  3. 任务处理完后,状态模式根据动作(比如「提交」)迁移到下一个状态。
  4. 重复 2-3 步,直到状态为「结束」。

避坑指南

最后分享几个我踩过的坑:

  • 状态对象不要持有业务数据。我曾经把审批人信息放在状态对象里,结果不同流程实例共用同一个状态对象,数据全乱了。状态对象应该是无状态的,数据放上下文。
  • 责任链不要搞成循环。有一次我写了个责任链,处理器 A 调 B,B 调 C,C 又调 A……直接栈溢出。责任链必须是单向的,或者用 break 机制终止。
  • 模板方法不要写太长。模板方法里如果塞了太多逻辑,子类重写起来很痛苦。保持骨架简洁,复杂逻辑拆到单独的方法里。
  • 状态迁移要考虑回退。比如「驳回」操作要从「部门审批」回到「草稿」。状态模式天然支持这种回退,只要在 next() 里返回对应的状态就行。

我的建议:刚开始用这三个模式时,先画状态图。把流程的所有状态和迁移画清楚,再写代码。状态图就是你的设计文档,比任何注释都管用。

好了,今天的内容就到这里。这三个模式配合起来,工作流引擎的核心骨架就搭好了。剩下的就是填充业务逻辑,比如审批策略、超时处理、并行节点等。但骨架稳了,加功能就不慌。


公众号:蓝海资料掘金营,微信deep3321