设计模式实战解读(十二):状态模式——干掉状态机里的 if-else 地狱

🔔 本文 6000+ 字深度原创,含完整代码示例和生产级落地方案。创作不易,如果对你有帮助,请点赞 👍 收藏 ⭐ 关注 🔥 三连支持,你的认可是我持续输出的最大动力!
本文是「设计模式实战解读」系列第十二篇。系列文章统一按照 定义 → 痛点场景 → 模式结构 → 核心实现 → 真实应用 → 常见变种 → 优缺点 → 避坑指南 → FAQ 的结构展开,每篇聚焦一个模式讲透。


一句话定义

状态模式(State):允许一个对象在其内部状态改变时改变它的行为,看起来就像改变了它的类。把每个状态的行为封装成独立的对象,用"状态切换"替代"满屏的 if-else"。

归属:行为型模式。


一、没有状态模式时的痛点

我们在 iPaaS 平台里有一个"流程实例",它的生命周期是这样的:

复制代码
创建(CREATED) → 排队中(QUEUED) → 执行中(RUNNING) → 成功(SUCCESS)
                                                  → 失败(FAILED) → 重试中(RETRYING) → 执行中(RUNNING)
                                                                                   → 最终失败(TERMINATED)
                                                  → 暂停(PAUSED) → 执行中(RUNNING)

七八个状态,每个操作(执行、暂停、取消、重试)对不同状态的行为都不一样。最原始的写法:

java 复制代码
public class FlowInstance {

    private String status; // CREATED / QUEUED / RUNNING / SUCCESS / FAILED / PAUSED ...

    public void execute() {
        if ("CREATED".equals(status)) {
            // 初始化上下文,投递到执行队列
            initContext();
            sendToQueue();
            status = "QUEUED";
        } else if ("QUEUED".equals(status)) {
            throw new BizException("流程正在排队中,请勿重复执行");
        } else if ("RUNNING".equals(status)) {
            throw new BizException("流程正在执行中");
        } else if ("PAUSED".equals(status)) {
            // 恢复执行
            resumeContext();
            sendToQueue();
            status = "RUNNING";
        } else if ("FAILED".equals(status)) {
            // 重试
            resetFailedNodes();
            sendToQueue();
            status = "RETRYING";
        } else if ("SUCCESS".equals(status) || "TERMINATED".equals(status)) {
            throw new BizException("流程已结束,无法执行");
        } else {
            throw new BizException("未知状态: " + status);
        }
    }

    public void pause() {
        if ("RUNNING".equals(status)) {
            saveSnapshot();
            status = "PAUSED";
        } else if ("QUEUED".equals(status)) {
            removeFromQueue();
            status = "PAUSED";
        } else {
            throw new BizException("当前状态不允许暂停: " + status);
        }
    }

    public void cancel() {
        if ("CREATED".equals(status) || "QUEUED".equals(status)) {
            status = "CANCELLED";
        } else if ("RUNNING".equals(status)) {
            killRunningTasks();
            status = "CANCELLED";
        } else if ("PAUSED".equals(status)) {
            cleanSnapshot();
            status = "CANCELLED";
        } else {
            throw new BizException("当前状态不允许取消: " + status);
        }
    }

    // retry() / resume() / complete() ... 每个方法又是一坨 if-else
}

问题一目了然:

  1. if-else 地狱 ------每个操作都要判断所有状态,execute() 方法 7 个分支,pause() 3 个分支,cancel() 4 个分支......
  2. 新增状态要改所有方法 ------加一个"审核中"状态,execute()pause()cancel()retry() 全要改
  3. 状态转换散落各处 ------"RUNNING → PAUSED"写在 pause() 里,"PAUSED → RUNNING"写在 execute() 里,看完整流转要翻遍所有方法
  4. 违反开闭原则 ------改一个状态的行为,要动整个 FlowInstance
  5. 测试困难------想测"暂停中的流程被取消",得先构造一个特定状态的完整对象

核心诉求:把每个状态的行为独立出来,状态之间切换清晰,新增状态不影响已有代码。


二、模式结构

复制代码
┌──────────────────────────────────┐
│      Context(上下文)             │
│  - state: State                  │ ← 持有当前状态
│  - instanceId: String            │
│  + execute()                     │ ← 委托给 state.execute()
│  + pause()                       │ ← 委托给 state.pause()
│  + cancel()                      │ ← 委托给 state.cancel()
│  + setState(State)               │ ← 状态切换入口
└───────────────┬──────────────────┘
                │ state.execute()
                ↓
┌──────────────────────────────────────────────────────────┐
│              State(状态接口)                            │
│  + execute(context): void                                │
│  + pause(context): void                                  │
│  + cancel(context): void                                 │
└───────┬──────┬──────┬──────┬──────┬──────┬───────────────┘
        │      │      │      │      │      │
        ↓      ↓      ↓      ↓      ↓      ↓
   Created  Queued Running Success Failed Paused ...

四个角色:

  • Context(上下文):持有当前状态对象的引用,对外暴露操作方法,所有操作委托给当前状态
  • State(状态接口):定义所有状态共有的操作契约
  • ConcreteState(具体状态):每个状态一个类,实现该状态下的行为,并决定切换到哪个状态
  • 状态转换 :由 ConcreteState 在操作完成后调用 context.setState() 完成切换

核心思想:把"状态判断"从 Context 移到 ConcreteState 里------每个状态自己管自己的行为,不再需要 if-else。


三、核心实现

3.1 定义状态接口

java 复制代码
/**
 * 流程实例状态接口------每个操作对应一种行为
 */
public interface FlowInstanceState {

    /** 执行/恢复执行 */
    void execute(FlowInstanceContext context);

    /** 暂停 */
    void pause(FlowInstanceContext context);

    /** 取消 */
    void cancel(FlowInstanceContext context);

    /** 完成(由引擎回调) */
    void complete(FlowInstanceContext context);

    /** 失败(由引擎回调) */
    void fail(FlowInstanceContext context, Exception error);

    /** 状态名称,用于日志和监控 */
    String name();
}

3.2 实现各具体状态

java 复制代码
/**
 * 创建状态------流程刚创建,还没投递到执行队列
 */
public class CreatedState implements FlowInstanceState {

    @Override
    public void execute(FlowInstanceContext context) {
        // 初始化执行上下文
        context.getFlowEngine().initContext(context.getInstanceId());
        // 投递到执行队列
        context.getFlowEngine().sendToQueue(context.getInstanceId());
        // 状态切换:CREATED → QUEUED
        context.setState(new QueuedState());
    }

    @Override
    public void pause(FlowInstanceContext context) {
        throw new BizException("流程尚未执行,无法暂停");
    }

    @Override
    public void cancel(FlowInstanceContext context) {
        // 创建态直接取消,无需清理
        context.setState(new CancelledState());
    }

    @Override
    public void complete(FlowInstanceContext context) {
        throw new BizException("流程尚未执行,无法完成");
    }

    @Override
    public void fail(FlowInstanceContext context, Exception error) {
        throw new BizException("流程尚未执行,无法标记失败");
    }

    @Override
    public String name() { return "CREATED"; }
}

/**
 * 执行中状态------节点正在逐个执行
 */
public class RunningState implements FlowInstanceState {

    @Override
    public void execute(FlowInstanceContext context) {
        throw new BizException("流程正在执行中,请勿重复触发");
    }

    @Override
    public void pause(FlowInstanceContext context) {
        // 保存执行快照(用于后续恢复)
        context.getFlowEngine().saveSnapshot(context.getInstanceId());
        // 中断当前节点
        context.getFlowEngine().interruptCurrentNode(context.getInstanceId());
        // 状态切换:RUNNING → PAUSED
        context.setState(new PausedState());
    }

    @Override
    public void cancel(FlowInstanceContext context) {
        // 杀死正在执行的任务
        context.getFlowEngine().killRunningTasks(context.getInstanceId());
        context.setState(new CancelledState());
    }

    @Override
    public void complete(FlowInstanceContext context) {
        context.getFlowEngine().finalizeInstance(context.getInstanceId());
        context.setState(new SuccessState());
    }

    @Override
    public void fail(FlowInstanceContext context, Exception error) {
        context.getFlowEngine().recordError(context.getInstanceId(), error);
        // 判断是否还有重试机会
        if (context.getRetryCount() < context.getMaxRetry()) {
            context.setState(new RetryingState());
        } else {
            context.setState(new TerminatedState());
        }
    }

    @Override
    public String name() { return "RUNNING"; }
}

/**
 * 暂停状态------快照已保存,等待恢复
 */
public class PausedState implements FlowInstanceState {

    @Override
    public void execute(FlowInstanceContext context) {
        // 从快照恢复上下文
        context.getFlowEngine().resumeFromSnapshot(context.getInstanceId());
        // 重新投递执行
        context.getFlowEngine().sendToQueue(context.getInstanceId());
        // 状态切换:PAUSED → RUNNING
        context.setState(new RunningState());
    }

    @Override
    public void pause(FlowInstanceContext context) {
        throw new BizException("流程已处于暂停状态");
    }

    @Override
    public void cancel(FlowInstanceContext context) {
        context.getFlowEngine().cleanSnapshot(context.getInstanceId());
        context.setState(new CancelledState());
    }

    @Override
    public void complete(FlowInstanceContext context) {
        throw new BizException("暂停中的流程无法完成");
    }

    @Override
    public void fail(FlowInstanceContext context, Exception error) {
        throw new BizException("暂停中的流程无法标记失败");
    }

    @Override
    public String name() { return "PAUSED"; }
}

3.3 Context 类------极其简洁

java 复制代码
/**
 * 流程实例上下文------所有操作委托给当前状态
 */
public class FlowInstanceContext {

    private FlowInstanceState state;
    private final String instanceId;
    private final FlowEngine flowEngine;
    private int retryCount;
    private int maxRetry;

    public FlowInstanceContext(String instanceId, FlowEngine flowEngine) {
        this.instanceId = instanceId;
        this.flowEngine = flowEngine;
        this.state = new CreatedState(); // 初始状态
        this.retryCount = 0;
        this.maxRetry = 3;
    }

    // 对外接口:全部委托给 state,Context 本身不含任何 if-else
    public void execute() { state.execute(this); }
    public void pause()   { state.pause(this); }
    public void cancel()  { state.cancel(this); }
    public void complete(){ state.complete(this); }
    public void fail(Exception e) { state.fail(this, e); }

    // 状态切换入口------由 ConcreteState 调用
    public void setState(FlowInstanceState newState) {
        FlowInstanceState oldState = this.state;
        this.state = newState;
        // 状态变更通知(日志/监控/持久化)
        flowEngine.onStateChanged(instanceId, oldState.name(), newState.name());
    }

    public String getCurrentState() { return state.name(); }
    public String getInstanceId() { return instanceId; }
    public FlowEngine getFlowEngine() { return flowEngine; }
    public int getRetryCount() { return retryCount; }
    public void incrementRetryCount() { retryCount++; }
    public int getMaxRetry() { return maxRetry; }
}

对比一下:重构前 Context 有七八个 if-else 分支,重构后 Context 只有委托调用------零 if-else。所有状态判断逻辑被分散到了各自的状态类里,每个状态类只管自己的事。


四、真实应用:iPaaS 连接器执行状态机

iPaaS 的连接器执行过程也有一个状态机。一个连接器调用从"发起请求"到"拿到结果",中间经历多个状态:
#mermaid-svg-0bt1wwYC9zunFUsW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0bt1wwYC9zunFUsW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0bt1wwYC9zunFUsW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0bt1wwYC9zunFUsW .error-icon{fill:#552222;}#mermaid-svg-0bt1wwYC9zunFUsW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0bt1wwYC9zunFUsW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0bt1wwYC9zunFUsW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0bt1wwYC9zunFUsW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0bt1wwYC9zunFUsW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0bt1wwYC9zunFUsW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0bt1wwYC9zunFUsW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0bt1wwYC9zunFUsW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0bt1wwYC9zunFUsW .marker.cross{stroke:#333333;}#mermaid-svg-0bt1wwYC9zunFUsW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0bt1wwYC9zunFUsW p{margin:0;}#mermaid-svg-0bt1wwYC9zunFUsW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0bt1wwYC9zunFUsW .cluster-label text{fill:#333;}#mermaid-svg-0bt1wwYC9zunFUsW .cluster-label span{color:#333;}#mermaid-svg-0bt1wwYC9zunFUsW .cluster-label span p{background-color:transparent;}#mermaid-svg-0bt1wwYC9zunFUsW .label text,#mermaid-svg-0bt1wwYC9zunFUsW span{fill:#333;color:#333;}#mermaid-svg-0bt1wwYC9zunFUsW .node rect,#mermaid-svg-0bt1wwYC9zunFUsW .node circle,#mermaid-svg-0bt1wwYC9zunFUsW .node ellipse,#mermaid-svg-0bt1wwYC9zunFUsW .node polygon,#mermaid-svg-0bt1wwYC9zunFUsW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0bt1wwYC9zunFUsW .rough-node .label text,#mermaid-svg-0bt1wwYC9zunFUsW .node .label text,#mermaid-svg-0bt1wwYC9zunFUsW .image-shape .label,#mermaid-svg-0bt1wwYC9zunFUsW .icon-shape .label{text-anchor:middle;}#mermaid-svg-0bt1wwYC9zunFUsW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0bt1wwYC9zunFUsW .rough-node .label,#mermaid-svg-0bt1wwYC9zunFUsW .node .label,#mermaid-svg-0bt1wwYC9zunFUsW .image-shape .label,#mermaid-svg-0bt1wwYC9zunFUsW .icon-shape .label{text-align:center;}#mermaid-svg-0bt1wwYC9zunFUsW .node.clickable{cursor:pointer;}#mermaid-svg-0bt1wwYC9zunFUsW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0bt1wwYC9zunFUsW .arrowheadPath{fill:#333333;}#mermaid-svg-0bt1wwYC9zunFUsW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0bt1wwYC9zunFUsW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0bt1wwYC9zunFUsW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0bt1wwYC9zunFUsW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0bt1wwYC9zunFUsW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0bt1wwYC9zunFUsW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0bt1wwYC9zunFUsW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0bt1wwYC9zunFUsW .cluster text{fill:#333;}#mermaid-svg-0bt1wwYC9zunFUsW .cluster span{color:#333;}#mermaid-svg-0bt1wwYC9zunFUsW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0bt1wwYC9zunFUsW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0bt1wwYC9zunFUsW rect.text{fill:none;stroke-width:0;}#mermaid-svg-0bt1wwYC9zunFUsW .icon-shape,#mermaid-svg-0bt1wwYC9zunFUsW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0bt1wwYC9zunFUsW .icon-shape p,#mermaid-svg-0bt1wwYC9zunFUsW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0bt1wwYC9zunFUsW .icon-shape .label rect,#mermaid-svg-0bt1wwYC9zunFUsW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0bt1wwYC9zunFUsW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0bt1wwYC9zunFUsW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0bt1wwYC9zunFUsW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 发起调用
连接成功
连接失败
响应成功
超时
响应异常
转换成功
转换异常
重试
重试
重试
空闲
连接中
请求中
连接失败
数据转换中
超时
请求失败
完成
转换失败

4.1 连接器执行状态接口

java 复制代码
public interface ConnectorExecState {
    void onEnter(ConnectorExecContext context);
    void execute(ConnectorExecContext context);
    void onTimeout(ConnectorExecContext context);
    void onError(ConnectorExecContext context, Exception e);
    String name();
}

4.2 关键状态实现

java 复制代码
/**
 * 请求中状态------HTTP请求已发出,等待响应
 */
public class RequestingState implements ConnectorExecState {

    @Override
    public void onEnter(ConnectorExecContext context) {
        // 启动超时计时器
        context.getTimerService().schedule(
            context.getRequestId(),
            context.getTimeout(),
            () -> context.onTimeout()  // 超时回调
        );
        // 发送HTTP请求
        HttpResponse response = context.getHttpClient().execute(
            context.getHttpRequest());
        context.setHttpResponse(response);
        // 响应成功:进入数据转换
        context.setState(new TransformingState());
    }

    @Override
    public void execute(ConnectorExecContext context) {
        throw new BizException("请求正在执行中");
    }

    @Override
    public void onTimeout(ConnectorExecContext context) {
        context.getTimerService().cancel(context.getRequestId());
        // 超时:判断是否重试
        if (context.canRetry()) {
            context.incrementRetry();
            context.setState(new IdleState()); // 回到空闲,重新发起
        } else {
            context.setState(new TimedOutState());
        }
    }

    @Override
    public void onError(ConnectorExecContext context, Exception e) {
        context.getTimerService().cancel(context.getRequestId());
        if (context.canRetry()) {
            context.incrementRetry();
            context.setState(new IdleState());
        } else {
            context.setState(new FailedState(e));
        }
    }

    @Override
    public String name() { return "REQUESTING"; }
}

4.3 状态切换自动持久化

我们在 setState() 里埋了一个钩子:每次状态切换自动写数据库 + 发消息。这样即使引擎宕机重启,也能从最后一个持久化的状态恢复执行:

java 复制代码
public class ConnectorExecContext {

    public void setState(ConnectorExecState newState) {
        ConnectorExecState oldState = this.state;
        this.state = newState;

        // 1. 持久化状态到数据库
        stateRepository.updateState(requestId, newState.name());

        // 2. 写入执行日志
        logService.logStateChange(requestId, oldState.name(), newState.name());

        // 3. 发布状态变更事件(监控/告警消费)
        eventPublisher.publish(new StateChangedEvent(
            requestId, oldState.name(), newState.name()));

        // 4. 触发新状态的 onEnter
        newState.onEnter(this);
    }
}

状态模式 + 持久化的组合威力:状态机每走一步都落库,宕机后重启时加载最后的状态对象,从断点继续执行。这就是我们 iPaaS 流程引擎断点续跑的核心机制。


五、状态模式 vs 策略模式

这两个模式的结构几乎一模一样------都是一个 Context 持有一个接口引用,都有多个实现类。很多人分不清。

对比维度 状态模式(State) 策略模式(Strategy)
切换时机 运行时自动切换(状态流转) 由调用方主动选择
状态之间 有流转关系,A→B→C 彼此独立,无流转
谁决定切换 状态对象自己(context.setState() Context 或调用方
核心目的 消除状态判断的 if-else 消除算法选择的 if-else
典型场景 订单/审批/流程实例状态机 排序算法/折扣策略/路由策略

一句话区分:策略模式是"我选一个工具来用",状态模式是"我变成另一个人了"。

举个例子:

java 复制代码
// 策略模式:调用方选择用哪个折扣算法
context.setStrategy(new VipDiscountStrategy());
context.calculatePrice(order);

// 状态模式:状态自动流转,调用方不知道也不关心
context.execute(); // CREATED → QUEUED → RUNNING → SUCCESS,自动的

六、常见变种

6.1 枚举状态(轻量版)

当状态行为比较简单时,用枚举代替状态类,减少类的数量:

java 复制代码
public enum SimpleFlowState {
    CREATED {
        @Override
        public void execute(FlowInstanceContext ctx) {
            ctx.getEngine().sendToQueue(ctx.getId());
            ctx.setState(QUEUED);
        }
        @Override
        public void cancel(FlowInstanceContext ctx) {
            ctx.setState(CANCELLED);
        }
    },
    QUEUED {
        @Override
        public void execute(FlowInstanceContext ctx) {
            throw new BizException("排队中");
        }
        @Override
        public void cancel(FlowInstanceContext ctx) {
            ctx.getEngine().removeFromQueue(ctx.getId());
            ctx.setState(CANCELLED);
        }
    },
    CANCELLED {
        @Override
        public void execute(FlowInstanceContext ctx) {
            throw new BizException("已取消");
        }
        @Override
        public void cancel(FlowInstanceContext ctx) {
            throw new BizException("已经是取消状态");
        }
    };

    public abstract void execute(FlowInstanceContext ctx);
    public abstract void cancel(FlowInstanceContext ctx);
}

适合状态数量少(≤5 个)、行为简单的场景。状态多了还是拆类更清晰。

6.2 状态表驱动(配置化)

把状态转换规则从代码搬到配置表,运行时加载:

复制代码
源状态      | 事件        | 目标状态    | 动作
CREATED    | execute     | QUEUED     | initContext + sendToQueue
QUEUED     | dequeue     | RUNNING    | startExecution
RUNNING    | complete    | SUCCESS    | finalizeInstance
RUNNING    | fail        | RETRYING   | incrementRetry
RETRYING   | execute     | RUNNING    | resumeExecution
PAUSED     | execute     | RUNNING    | resumeFromSnapshot
java 复制代码
// 状态表驱动的状态机
public class TableDrivenStateMachine {
    private final Map<String, Map<String, Transition>> transitions;

    public void fireEvent(String event) {
        Transition t = transitions.get(currentState).get(event);
        if (t == null) {
            throw new BizException("状态 " + currentState + " 不支持事件 " + event);
        }
        t.getAction().execute(context);
        currentState = t.getTargetState();
    }
}

优势:新增状态只需加配置行,不改代码。我们的 iPaaS 流程引擎就是表驱动的------用户在界面上配置"失败后重试 3 次"、"超时后转人工",底层就是状态表的行。

6.3 层级状态(Hierarchical State)

当多个状态有共同行为时,用继承避免重复:

java 复制代码
/**
 * 活跃状态基类------RUNNING / RETRYING / PAUSED 都属于"活跃"
 */
public abstract class ActiveState implements FlowInstanceState {
    @Override
    public void cancel(FlowInstanceContext context) {
        // 所有活跃状态都可以被取消
        context.getFlowEngine().cleanup(context.getInstanceId());
        context.setState(new CancelledState());
    }
}

public class RunningState extends ActiveState { /* ... */ }
public class RetryingState extends ActiveState { /* ... */ }
public class PausedState extends ActiveState { /* ... */ }

RUNNING / RETRYING / PAUSED 都继承了 ActiveState 的 cancel() 实现,不需要每个类都写一遍。


七、状态转换的可视化

状态模式最大的好处之一:状态流转天然形成一张图 。把每个状态类的 setState() 调用提取出来,就能自动生成状态转换图。
#mermaid-svg-AoJje6vkAQgHFw6q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AoJje6vkAQgHFw6q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AoJje6vkAQgHFw6q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AoJje6vkAQgHFw6q .error-icon{fill:#552222;}#mermaid-svg-AoJje6vkAQgHFw6q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AoJje6vkAQgHFw6q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AoJje6vkAQgHFw6q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AoJje6vkAQgHFw6q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AoJje6vkAQgHFw6q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AoJje6vkAQgHFw6q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AoJje6vkAQgHFw6q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AoJje6vkAQgHFw6q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AoJje6vkAQgHFw6q .marker.cross{stroke:#333333;}#mermaid-svg-AoJje6vkAQgHFw6q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AoJje6vkAQgHFw6q p{margin:0;}#mermaid-svg-AoJje6vkAQgHFw6q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AoJje6vkAQgHFw6q .cluster-label text{fill:#333;}#mermaid-svg-AoJje6vkAQgHFw6q .cluster-label span{color:#333;}#mermaid-svg-AoJje6vkAQgHFw6q .cluster-label span p{background-color:transparent;}#mermaid-svg-AoJje6vkAQgHFw6q .label text,#mermaid-svg-AoJje6vkAQgHFw6q span{fill:#333;color:#333;}#mermaid-svg-AoJje6vkAQgHFw6q .node rect,#mermaid-svg-AoJje6vkAQgHFw6q .node circle,#mermaid-svg-AoJje6vkAQgHFw6q .node ellipse,#mermaid-svg-AoJje6vkAQgHFw6q .node polygon,#mermaid-svg-AoJje6vkAQgHFw6q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AoJje6vkAQgHFw6q .rough-node .label text,#mermaid-svg-AoJje6vkAQgHFw6q .node .label text,#mermaid-svg-AoJje6vkAQgHFw6q .image-shape .label,#mermaid-svg-AoJje6vkAQgHFw6q .icon-shape .label{text-anchor:middle;}#mermaid-svg-AoJje6vkAQgHFw6q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AoJje6vkAQgHFw6q .rough-node .label,#mermaid-svg-AoJje6vkAQgHFw6q .node .label,#mermaid-svg-AoJje6vkAQgHFw6q .image-shape .label,#mermaid-svg-AoJje6vkAQgHFw6q .icon-shape .label{text-align:center;}#mermaid-svg-AoJje6vkAQgHFw6q .node.clickable{cursor:pointer;}#mermaid-svg-AoJje6vkAQgHFw6q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AoJje6vkAQgHFw6q .arrowheadPath{fill:#333333;}#mermaid-svg-AoJje6vkAQgHFw6q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AoJje6vkAQgHFw6q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AoJje6vkAQgHFw6q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AoJje6vkAQgHFw6q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AoJje6vkAQgHFw6q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AoJje6vkAQgHFw6q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AoJje6vkAQgHFw6q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AoJje6vkAQgHFw6q .cluster text{fill:#333;}#mermaid-svg-AoJje6vkAQgHFw6q .cluster span{color:#333;}#mermaid-svg-AoJje6vkAQgHFw6q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AoJje6vkAQgHFw6q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AoJje6vkAQgHFw6q rect.text{fill:none;stroke-width:0;}#mermaid-svg-AoJje6vkAQgHFw6q .icon-shape,#mermaid-svg-AoJje6vkAQgHFw6q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AoJje6vkAQgHFw6q .icon-shape p,#mermaid-svg-AoJje6vkAQgHFw6q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AoJje6vkAQgHFw6q .icon-shape .label rect,#mermaid-svg-AoJje6vkAQgHFw6q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AoJje6vkAQgHFw6q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AoJje6vkAQgHFw6q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AoJje6vkAQgHFw6q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} execute
dequeue
complete
fail+可重试
fail+无重试
pause
execute
execute
cancel
cancel
cancel
cancel
Created
Queued
Running
Success
Retrying
Terminated
Paused
Cancelled

建议在项目里维护一份这样的图------新人入职看一眼就懂了,比自己翻代码快十倍。


八、优缺点

优点

  1. 消除 if-else------每个状态一个类,行为内聚,不再有大段条件判断
  2. 符合开闭原则------新增状态只需加一个类,不改已有代码
  3. 状态转换集中管理------每个状态类明确声明"我能切到哪些状态"
  4. 易于测试------测试某个状态的行为,只需构造对应的状态对象
  5. 状态可视化------状态转换天然可以画出状态图,文档自动生成

缺点

  1. 类数量增多------7 个状态就是 7 个类 + 1 个接口,小项目可能"杀鸡用牛刀"
  2. 状态间共享逻辑------如果多个状态有共同行为,需要引入基类或辅助类,否则代码重复
  3. 不适合简单场景------只有 2-3 个状态且行为简单时,if-else 反而更直观

九、避坑指南

坑 1:状态对象持有太多引用

症状:每个 ConcreteState 都要注入 10 个 Service,构造参数爆炸。

修复:把依赖集中到 Context 里,状态对象通过 Context 获取所需服务。状态类本身保持"瘦"------只写状态判断和流转逻辑。

java 复制代码
// 错误:状态对象自己注入一堆依赖
public class RunningState implements FlowInstanceState {
    @Autowired private FlowEngine engine;      // ← 状态类不应该有 @Autowired
    @Autowired private LogService logService;
    @Autowired private MetricsService metrics;
}

// 正确:通过 Context 获取
public class RunningState implements FlowInstanceState {
    @Override
    public void complete(FlowInstanceContext context) {
        context.getFlowEngine().finalizeInstance(context.getId()); // 通过 context 获取
        context.setState(new SuccessState());
    }
}

坑 2:忘记处理"非法转换"

症状:状态类里只写了"允许的操作",没写"不允许的操作"------调用方传了不该传的操作,直接 NPE 或静默忽略。

修复:接口里的每个方法,在每个状态类里都要有明确的实现------要么正常处理,要么抛出明确的异常。

java 复制代码
// 错误:SuccessState 没实现 pause(),基类默认实现什么都不做
public class SuccessState implements FlowInstanceState {
    // 缺少 pause() 实现 → 调用方以为暂停成功了
}

// 正确:明确拒绝
public class SuccessState implements FlowInstanceState {
    @Override
    public void pause(FlowInstanceContext context) {
        throw new BizException("已完成的流程无法暂停");
    }
}

坑 3:状态切换和业务操作顺序搞反

症状:先切状态再执行业务操作------如果业务操作失败了,状态已经变了,数据不一致。

修复先执行业务操作,再切换状态。如果业务操作抛异常,状态不会切换。

java 复制代码
// 错误:先切状态
context.setState(new RunningState()); // ← 状态已经是 RUNNING 了
context.getEngine().startExecution();  // ← 如果这里失败了?状态不一致!

// 正确:先执行,再切状态
context.getEngine().startExecution();  // ← 失败直接抛异常
context.setState(new RunningState());  // ← 只有成功才切换

坑 4:状态对象有状态

症状:ConcreteState 类里有成员变量(比如计数器、时间戳),多个 Context 共享同一个状态对象时会串数据。

修复 :状态对象保持无状态------所有上下文信息都从 Context 参数里获取。如果确实需要状态类的单例共享,确保线程安全。

java 复制代码
// 错误:状态对象有成员变量
public class RetryingState implements FlowInstanceState {
    private int retryCount = 0; // ← 多个流程实例共享这个计数器!
}

// 正确:从 Context 获取
public class RetryingState implements FlowInstanceState {
    @Override
    public void execute(FlowInstanceContext context) {
        int count = context.getRetryCount(); // ← 从 Context 获取,实例隔离
    }
}

十、常见问题(FAQ)

Q:状态模式和 if-else 什么时候用哪个?

A:经验法则------状态 ≤ 3 个且每个状态只有 1-2 个操作,用 if-else 就够了。超过 3 个状态、或每个状态有 3 个以上操作、或未来可能新增状态,就上状态模式。状态模式的"前期成本"会在后续维护中连本带利收回。

Q:状态对象需要是单例吗?

A:看情况。如果状态对象无状态(推荐做法),可以做成单例,减少对象创建开销。用枚举天然就是单例。如果状态对象有差异化参数(比如 FailedState 需要携带错误信息),每次创建新实例。

Q:Spring StateMachine 框架怎么样?

A:Spring StateMachine 功能很全(支持层级状态、Guard/Action、持久化),但学习曲线陡、配置复杂。对于简单的状态机(5-8 个状态),手写状态模式更轻量、更可控。状态数量超过 15 个或需要复杂的 Guard 条件时,可以考虑引入框架。

Q:状态模式和工作流引擎有什么区别?

A:状态模式是代码层面 的设计模式,用 Java 类实现状态流转。工作流引擎(如 Camunda、我们的 iPaaS 流程引擎)是平台层面的工具,用可视化界面定义流程,底层可能用了状态模式。工作流引擎还额外提供了可视化编排、人工审批、超时处理等能力。

Q:如何在数据库中持久化状态?

A:推荐存状态的枚举字符串 (如 "RUNNING"),不要存数字。配合状态转换表做校验------每次更新前检查 旧状态 → 新状态 是否合法,防止并发场景下的状态跳跃。乐观锁(版本号)也是个好补充。

Q:并发场景下状态切换安全吗?

A:需要额外保障。常见方案:① 数据库行锁(UPDATE ... WHERE status = 'RUNNING');② 分布式锁(Redis/Zookeeper);③ 乐观锁(版本号)。状态模式本身不解决并发问题,并发是基础设施层的职责。


十一、小结

状态模式是解决"if-else 地狱"最直接的武器。三句话总结:

  1. 每个状态一个类------行为内聚、职责清晰,新增状态只加类不改旧代码
  2. 状态切换由状态对象自己决定------Context 只做委托,不参与状态判断
  3. 状态对象保持无状态------所有上下文信息从 Context 获取,避免多实例串数据

从 CREATED → QUEUED → RUNNING → SUCCESS,每一个状态转换都是代码里的一行 context.setState()------清晰、可追踪、可测试。当你发现自己在写第三个 if-else 判断同一个状态变量时,就是引入状态模式的最好时机。


预告 :下一篇我们聊聊备忘录模式------如何优雅地实现"撤销"和"断点恢复",让流程实例的快照管理不再是一坨序列化代码。


标签:#设计模式 #状态模式 #State #行为型模式 #状态机 #架构设计 #iPaaS #流程引擎 #if-else #代码质量 #设计模式实战 #技术分享 #Java #研发效能

相关推荐
我爱cope3 小时前
【Agent智能体24 | 规划-创建和执行LLM计划】
人工智能·设计模式·语言模型·职场和发展
Hillain4 小时前
软件设计师设计模式
java·开发语言·经验分享·笔记·算法·设计模式·软考
zhengfei61112 小时前
第3章 Agent 类型分类与设计模式
设计模式
刀法如飞13 小时前
一文搞懂DDD 领域驱动设计思想原理
设计模式·架构·代码规范
折哥的程序人生 · 物流技术专研16 小时前
Java 23 种设计模式:从踩坑到精通 | 原型模式 —— 克隆对象,深拷贝与浅拷贝的坑你踩过吗?
java·设计模式·架构·原型模式·单一职责原则
超梦dasgg1 天前
详细讲解 AI 上下文(Context)
人工智能·状态模式
geovindu1 天前
python: Broadcast Pattern
开发语言·python·设计模式·广播模式
我爱cope1 天前
【Agent智能体22 | 构建AI工作流的技巧-延迟、成本优化】
人工智能·设计模式·语言模型·职场和发展