GoF设计模式——状态模式

0 阅读20分钟

本文是【GoF设计模式】系列第20篇,更多内容欢迎关注公众号:咖啡八杯

image.png

前言

为什么需要状态模式?

假设正在实现电商订单系统,订单有五种状态:待支付、已支付、已发货、已完成、已取消。每种状态下可执行的操作都不一样——待支付可以支付、可以取消;已发货只能确认收货;已完成不能再动。没有状态模式的时候,代码大概长这样:

class Order {
    private String status = "PENDING";

    public void pay() {
        if ("PENDING".equals(status)) {
            System.out.println("支付成功");
            status = "PAID";
        } else if ("PAID".equals(status)) {
            throw new RuntimeException("已支付,不能重复支付");
        } else if ("SHIPPED".equals(status)) {
            throw new RuntimeException("已发货,不能支付");
        } else if ("CANCELLED".equals(status)) {
            throw new RuntimeException("已取消,不能支付");
        }
        // ...
    }

    public void ship() {
        // 同样的 if-else 判断五种状态 ...
    }

    public void confirm() {
        // 同样的 if-else 判断五种状态 ...
    }

    public void cancel() {
        // 同样的 if-else 判断五种状态 ...
    }
}

问题一目了然:状态判断和业务逻辑混在一起。5 种状态 × 4 个操作 = 20 个条件分支散落各处。新增一个"退货中"状态?四个方法都要改一遍。想搞清楚"已支付"状态能干什么?得翻遍所有方法找带 "PAID" 的分支。

换一种思路: "当前是什么状态"应该由一个专门的对象来承担,而不是由字符串常量来标记。状态模式解决的就是这个问题——把每种状态封装成一个类,让状态自己决定"我这个状态下能做什么、做完之后变成什么",Order 只负责持有当前状态并转发调用。

概念

状态模式(State Pattern)是一种行为型设计模式,核心思想是允许一个对象在内部状态改变时改变它的行为,使对象看起来好像修改了它的类型

从理论上看,状态模式是有限状态机(Finite State Machine, FSM) 的面向对象实现:FSM 描述"一组状态、若干事件、以及状态之间的转换规则",状态模式则把每个状态变成一个对象、把转换规则封装进状态类内部。建立这个对应关系后,遇到"状态机"一词便能直接映射到状态模式,不会当作陌生概念。

可以把它想象成红绿灯:红灯状态下车辆要停下,绿灯状态下允许通行,黄灯状态下要减速——完全不同的行为,但对外都是"同一个红绿灯"。路口本身不需要有一大堆 if (color == red) ... 的逻辑,只要"当前是哪个灯就执行那个灯的规则",切换灯色时行为自然跟着变。

状态模式涉及三个角色:

  • Context(上下文) :持有一个当前状态的引用,把行为委托给当前状态对象,并提供状态切换的入口
  • State(状态接口) :定义 Context 在某一状态下可能执行的所有行为接口
  • ConcreteState(具体状态) :实现状态接口,负责该状态下的具体行为,并决定操作完成后转向哪个状态
classDiagram
    direction BT

    class Context {
        -state: State
        +request()
    }
    class State {
        <<interface>>
        +handle() State
    }
    class ConcreteStateA {
        +handle() State
    }
    class ConcreteStateB {
        +handle() State
    }

    ConcreteStateA ..|> State : 实现
    ConcreteStateB ..|> State : 实现
    Context o--> State : 持有当前状态
    ConcreteStateA ..> ConcreteStateB : 转换
    ConcreteStateB ..> ConcreteStateA : 转换

图中各类之间的关系:Context 持有一个 State 引用,request 方法把调用委托给当前状态。ConcreteStateAConcreteStateB 各自实现 handle 方法,处理当前状态下的行为并返回下一个状态。状态之间通过"返回新状态对象"完成转换——ConcreteStateAhandle 可能返回 ConcreteStateB 的实例,Context 用返回值更新自身的当前状态。

实现

状态模式的落地有两种常见变体:一种是 GoF 原书的"状态类持有 Context 引用,主动调用 Context 的 setState 切换";另一种更现代的做法是"状态类的方法直接返回下一个状态对象,由 Context 根据返回值更新"。第二种变体更简洁——状态类不需要持有 Context 引用,耦合更彻底地降到零。本文主推这种变体。

基础实现

定义 State 接口,声明 handle 方法返回 State——这个返回值就是操作完成后要转向的下一个状态。ConcreteStateAConcreteStateB 各自实现 handle,处理完自身逻辑后返回对方的实例。Context 持有当前状态,在 request 方法中调用状态的 handle 并用返回值更新自身状态。

// State:状态接口,方法返回下一个状态
interface State {
    State handle();
}

// ConcreteStateA:具体状态 A
class ConcreteStateA implements State {
    public State handle() {
        System.out.println("状态 A 的处理逻辑");
        return new ConcreteStateB();        // 返回下一个状态
    }
}

// ConcreteStateB:具体状态 B
class ConcreteStateB implements State {
    public State handle() {
        System.out.println("状态 B 的处理逻辑");
        return new ConcreteStateA();
    }
}

// Context:上下文
class Context {
    private State state;

    public Context(State initialState) {
        this.state = initialState;
    }

    public void request() {
        this.state = state.handle();        // 用返回值更新当前状态
    }
}

// 客户端
class Client {
    public static void main(String[] args) {
        Context context = new Context(new ConcreteStateA());
        context.request();      // 状态 A 的处理逻辑 → 切换到 B
        context.request();      // 状态 B 的处理逻辑 → 切换回 A
    }
}

角色对照

  • Context(上下文)Context,持有当前状态并转发调用
  • State(状态接口)State,声明 handle 返回下一个状态
  • ConcreteState(具体状态)ConcreteStateAConcreteStateB

关键点:状态之间的转换关系完全封装在状态类内部——ConcreteStateA.handle 明确知道自己下一个要变成 ConcreteStateB,Context 完全不关心具体的转换规则。这带来一个天然特性:新增状态或修改转换规则时,只需要改相关的状态类,Context 和其他状态类一行不动。缺点是状态类之间存在依赖关系——ConcreteStateA 引用了 ConcreteStateB,如果状态很多,这种引用关系会形成一张"状态图",需要有意识地管理。

引入一个具体场景:电商订单系统,五种状态之间按业务规则流转。每种状态类实现四个操作方法(payshipconfirmcancel),能执行的操作返回新状态,不能执行的操作返回 this(保持当前状态)。

// State:订单状态接口
interface OrderState {
    OrderState pay(OrderContext ctx);
    OrderState ship(OrderContext ctx);
    OrderState confirm(OrderContext ctx);
    OrderState cancel(OrderContext ctx);
}

// ConcreteState:待支付状态
class PendingPayment implements OrderState {
    public OrderState pay(OrderContext ctx) {
        System.out.println("支付成功,订单号:" + ctx.getOrderId());
        return new Paid();
    }
    public OrderState ship(OrderContext ctx) {
        System.out.println("错误:待支付订单不能发货");
        return this;                        // 保持当前状态
    }
    public OrderState confirm(OrderContext ctx) {
        System.out.println("错误:待支付订单不能确认收货");
        return this;
    }
    public OrderState cancel(OrderContext ctx) {
        System.out.println("订单已取消:" + ctx.getOrderId());
        return new Cancelled();
    }
}

// ConcreteState:已支付状态
class Paid implements OrderState {
    public OrderState pay(OrderContext ctx) {
        System.out.println("错误:已支付,不能重复支付");
        return this;
    }
    public OrderState ship(OrderContext ctx) {
        System.out.println("订单已发货:" + ctx.getOrderId());
        return new Shipped();
    }
    public OrderState confirm(OrderContext ctx) {
        System.out.println("错误:已支付订单尚未发货");
        return this;
    }
    public OrderState cancel(OrderContext ctx) {
        System.out.println("订单已取消,金额原路退回");
        return new Cancelled();
    }
}

// ConcreteState:已发货状态
class Shipped implements OrderState {
    public OrderState pay(OrderContext ctx) { return this; }
    public OrderState ship(OrderContext ctx) { return this; }
    public OrderState confirm(OrderContext ctx) {
        System.out.println("确认收货成功:" + ctx.getOrderId());
        return new Completed();
    }
    public OrderState cancel(OrderContext ctx) {
        System.out.println("错误:已发货订单请申请退货");
        return this;
    }
}

// ConcreteState:已完成状态(终态)
class Completed implements OrderState {
    public OrderState pay(OrderContext ctx) { return this; }
    public OrderState ship(OrderContext ctx) { return this; }
    public OrderState confirm(OrderContext ctx) { return this; }
    public OrderState cancel(OrderContext ctx) { return this; }
}

// ConcreteState:已取消状态(终态)
class Cancelled implements OrderState {
    public OrderState pay(OrderContext ctx) { return this; }
    public OrderState ship(OrderContext ctx) { return this; }
    public OrderState confirm(OrderContext ctx) { return this; }
    public OrderState cancel(OrderContext ctx) { return this; }
}

// Context:订单
class OrderContext {
    private String orderId;
    private OrderState state;

    public OrderContext(String orderId) {
        this.orderId = orderId;
        this.state = new PendingPayment();  // 初始状态
    }

    public String getOrderId() { return orderId; }

    // 每个业务方法把调用委托给当前状态,用返回值更新状态
    public void pay()     { state = state.pay(this); }
    public void ship()    { state = state.ship(this); }
    public void confirm() { state = state.confirm(this); }
    public void cancel()  { state = state.cancel(this); }
}

角色对照

  • Context(上下文)OrderContext,持有订单数据和当前状态
  • State(状态接口)OrderState,声明四个业务方法
  • ConcreteState(具体状态)PendingPaymentPaidShippedCompletedCancelled

关键点:每个状态类只关心"我这个状态下每个操作该怎么响应、之后变成什么"——PendingPayment 只需要知道"支付后变成 Paid、取消后变成 Cancelled",其他状态怎么转换它一概不管。业务扩展友好:新增"退货中"状态只要写一个 Refunding 类,同时修改 Completed.pay 之类的相关方法,其他状态类一行不动。终态(CompletedCancelled)的四个方法都返回 this,代码简洁明确——"到我这里就是终点,任何操作都不再改变状态"。

上面的状态都是普通类,每次切换状态都 new 一个新对象。Java 还有一种更省事的写法——用 enum 实现状态接口:枚举常量本身就是单例,切换状态时直接返回另一个常量,连 new 都省了。沿用前文的 State 接口(State handle()),A、B 两个状态切换的枚举版本如下。

// 枚举实现状态模式:状态常量天然单例,无需 new
enum StateEnum implements State {
    STATE_A {
        public State handle() {
            System.out.println("状态 A 的处理逻辑");
            return STATE_B;                 // 直接返回另一个常量
        }
    },
    STATE_B {
        public State handle() {
            System.out.println("状态 B 的处理逻辑");
            return STATE_A;
        }
    };
}

枚举实现的代价是状态集合在编译期固定,无法运行时新增状态;转换关系也写在每个常量内部,状态多了会显得拥挤。状态数量少、转换稳定时用枚举最清爽;状态会动态扩展或转换规则复杂时,用类更灵活。

总结

本质:把不同状态下的行为拆分成独立的类,让对象在状态变化时切换实现类,从而"看起来像换了个类"。

什么时候用

  • 对象的行为完全由内部状态决定,且状态之间有明确转换规则(订单、审批、TCP、游戏角色)
  • 代码里出现大量基于状态的 if-else/switch 判断,且每个分支的行为逻辑各不相同
  • 状态数量会增长——新增状态时不希望改动所有已有代码
  • 需要显式建模状态机——业务方通过状态类名就能理解流转规则

什么时候不用

  • 只有 2-3 个状态且逻辑简单——一个枚举 + 一个 switch 更直观
  • 状态之间没有转换规则,本质上是"数据分类"而不是"行为分类"
  • 状态频繁变化且每次都新建对象——性能敏感场景考虑用状态单例复用
  • 对象没有"行为随状态变化"的需求——单纯的数据状态字段不需要模式化

简单记忆:状态自己变身法,转换规则藏于内;if-else 都下岗,对象仿佛换新装。

相似模式区分

总览

模式核心意图典型场景
状态根据内部状态改变对象行为,对象自己驱动状态转换订单流转、审批、TCP 状态机
策略封装可互换的算法族,由外部选择使用哪个排序算法、支付方式、折扣策略
责任链请求沿链条依次传递,由多个处理者协同处理拦截器、审批链、日志过滤

简单记忆:状态"自己管自己变身",策略"别人来选我一个",责任链"链上一个接一个"。

状态 vs 策略

两者的类图几乎一模一样——Context + 接口 + 多个实现类。刚学的时候最容易搞混,但它们回答的是完全不同的问题:

维度状态模式策略模式
核心意图根据内部状态改变对象行为封装可互换的算法族,运行时切换
谁决定用哪个实现对象自身(通过状态转换)外部客户端(手动注入)
实现类是否知道彼此是——每个状态类知道自己转向哪个状态否——策略之间完全独立
切换时机事件触发,自动切换客户端调用 setStrategy 手动切换
典型场景订单流转、审批流、游戏角色折扣算法、支付方式、排序策略

逐步区分法

  • 如果行为由对象自身的内部状态决定,且状态之间自动转换 → 选状态
  • 如果行为由外部客户端选择,不同实现之间互相独立 → 选策略
  • 如果不确定:问自己"谁来切换"——对象自己切换用状态,外部切换用策略
  • 如果实现类会引用其他实现类(return new StateB())→ 一定是状态

简单记忆口诀:状态"自己管自己变身",策略"别人来选我一个"。

推荐:两者都能用时优先选策略——它更简单、耦合更低。只有在需要显式建模状态机、状态之间存在明确转换规则的场景才用状态模式。策略是"我需要一个算法,你告诉我用哪个",状态是"我的行为由我此刻的样子决定,别人别操心"。

状态 vs 责任链

两者在"根据情况分派处理"这个场景上有重叠——审批流用状态模式可以做,用责任链也可以做——但内部结构完全不同:

维度状态模式责任链模式
核心意图同一对象在不同状态下表现出不同行为请求沿处理者链条依次传递,直到被处理
对象数量一个 Context + 多个状态类,Context 唯一多个 Handler 组成链,请求经过所有 Handler
控制流Context 委托给当前状态,只调用一个状态类请求从头传到尾,多个 Handler 依次尝试
典型场景订单流转、TCP 状态机、游戏角色Servlet 过滤器、拦截器、审批链

逐步区分法

  • 如果核心是"同一个对象在不同时刻表现不同" → 选状态
  • 如果核心是"同一个请求经过多个处理者" → 选责任链
  • 审批场景:如果关心"文档当前处于哪个审批阶段" → 状态;如果关心"审批人一个接一个盖章" → 责任链
  • 状态转换是"我变成谁",责任链传递是"我把请求交给下一个"

简单记忆口诀:状态是"一个对象换角色",责任链是"一个请求走链条"。

推荐:审批系统这类场景两者都能用,选择的关键在于——如果流程节点数量固定、每个节点有明确的业务含义(草稿/主管审批/经理审批),选状态;如果处理者会动态增减、链条本身可能重排,选责任链。前者建模的是"文档的生命周期",后者建模的是"事件的处理流水线"。

练习题目

智能台灯

题目描述:小明家有一盏智能台灯,有三种状态:关闭(Off)、开启(On)和闪烁(Blinking)。台灯初始状态为关闭。

台灯可通过两种操作控制:

按下按钮(button):

  • 关闭 → 开启,输出 Light is ON
  • 开启 → 闪烁,输出 Light is Blinking
  • 闪烁 → 关闭,输出 Light is OFF

感应触发(sensor):

  • 关闭 → 闪烁,输出 Light is Blinking
  • 开启 → 关闭,输出 Light is OFF
  • 闪烁 → 开启,输出 Light is ON

输入描述:第一行是一个整数 n(1 ≤ n ≤ 1000),表示操作次数。接下来的 n 行,每行包含一个字符串,表示操作类型(button 或 sensor)。

输出描述:对于每个操作,输出一行,表示执行该操作后灯的状态。

输入示例

5
button
button
button
sensor
sensor

输出示例

Light is ON
Light is Blinking
Light is OFF
Light is Blinking
Light is ON

状态转换表

当前状态buttonsensor
Off→ On→ Blinking
On→ Blinking→ Off
Blinking→ Off→ On

解题思路:三种状态 × 两种操作 = 6 种转换。用返回新状态的变体:每个状态类实现 handleButtonhandleSensor 两个方法,直接返回转换后的下一个状态对象;Light 作为 Context 持有当前状态,接收操作后用返回值更新自身状态。

角色对应

  • Context(上下文)Light,持有当前状态并转发操作
  • State(状态接口)State,声明 handleButtonhandleSensor 都返回 State
  • ConcreteState(具体状态)StateOffStateOnStateBlinking

核心逻辑

  1. 每个状态类实现两个方法,直接输出信息并返回下一个状态
  2. Light 收到 buttonsensor 操作后调用当前状态对应的方法,用返回值更新自身
  3. 状态转换关系全部封装在状态类内部,Light 只是转发调用
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        Light light = new Light();
        while (n-- > 0) {
            String type = sc.next();
            if ("button".equals(type)) {
                light.button();
            } else {
                light.sensor();
            }
        }
    }
}

// State:状态接口
interface State {
    State handleButton();
    State handleSensor();
}

// ConcreteState:关闭状态
class StateOff implements State {
    public State handleButton() {
        System.out.println("Light is ON");
        return new StateOn();
    }
    public State handleSensor() {
        System.out.println("Light is Blinking");
        return new StateBlinking();
    }
}

// ConcreteState:开启状态
class StateOn implements State {
    public State handleButton() {
        System.out.println("Light is Blinking");
        return new StateBlinking();
    }
    public State handleSensor() {
        System.out.println("Light is OFF");
        return new StateOff();
    }
}

// ConcreteState:闪烁状态
class StateBlinking implements State {
    public State handleButton() {
        System.out.println("Light is OFF");
        return new StateOff();
    }
    public State handleSensor() {
        System.out.println("Light is ON");
        return new StateOn();
    }
}

// Context:智能台灯
class Light {
    private State state;

    public Light() {
        this.state = new StateOff();        // 初始状态为关闭
    }

    public void button() {
        this.state = state.handleButton();  // 用返回值更新状态
    }

    public void sensor() {
        this.state = state.handleSensor();
    }
}

扩展:实际项目中的状态模式

电商订单流转

订单系统是状态模式最经典的应用——待支付、已支付、已发货、已完成、已取消这几种状态之间的流转规则清晰,每种状态下的可用操作各不相同。上文实现章节的业务代码就是这个场景的完整版本,几乎所有电商中台的订单服务都能看到类似结构。这种建模方式的最大好处是新增状态不改老代码——比如后来加入"退货中"状态,只需要新增一个 Refunding 类,其他四个状态类几乎不动,符合开闭原则。

// 客户端调用极简,看不到任何 if-else
OrderContext order = new OrderContext("ORD_001");
order.pay();        // 支付成功 → Paid
order.ship();       // 订单已发货 → Shipped
order.confirm();    // 确认收货成功 → Completed
order.cancel();     // 错误:订单已完成,不能取消(自动被 Completed 状态挡回)

订单状态变更后往往还要联动其他系统--支付成功要通知库存扣减、已发货要通知物流跟踪。这时状态模式常和观察者模式组合使用:状态切换时发布一个事件,库存、物流等组件订阅该事件各自处理。状态模式管"怎么变",观察者模式管"变了之后通知谁",两者分工天然互补。

状态持久化与恢复

实际项目里状态不能只活在内存--订单状态要写进数据库,服务重启后还得能恢复。但状态对象是内存里的类实例,没法直接落库。常见做法是Context 只存一个状态标识字段(枚举或字符串),持久化时只存这个标识;从数据库加载时,根据标识重建对应的状态对象。这样既保留了状态模式的行为封装,又解决了存储问题。

class OrderContext {
    private String orderId;
    private OrderState state;
    private String stateCode;         // 落库字段:只存状态标识

    // 从数据库恢复:根据标识重建状态对象
    public void restore() {
        this.state = switch (stateCode) {
            case "PendingPayment" -> new PendingPayment();
            case "Paid"            -> new Paid();
            case "Shipped"         -> new Shipped();
            case "Completed"       -> new Completed();
            case "Cancelled"       -> new Cancelled();
            default -> throw new IllegalStateException("未知状态:" + stateCode);
        };
    }

    // 状态切换后同步更新标识,供下次落库
    public void pay() {
        state = state.pay(this);
        stateCode = state.getClass().getSimpleName();  // 类名即状态标识
    }
}

状态标识和状态类的映射关系集中在一处(如上面的 switch 或一个 Map),新增状态时只需补一条映射,业务逻辑不受影响。

游戏角色状态机

游戏角色通常有多种状态:待机、移动、攻击、受击、死亡,每种状态下的输入响应和动画播放完全不同。Unity3D 的 Animator 控制器、虚幻引擎的 State Machine 本质上就是可视化的状态模式实现。除了行为切换外,游戏状态机还有一个特色——帧驱动的自动转换update 方法每帧被调用,攻击动画播完(比如 30 帧)后自动回到待机,这不是外部触发的,是状态自身根据内部计数器决定的。

class AttackingState implements CharacterState {
    private int frameCount = 0;

    public CharacterState update(CharacterContext ctx) {
        frameCount++;
        if (frameCount >= 30) {
            return new IdleState();         // 动画播完,自动回待机
        }
        return this;
    }

    public CharacterState hit(CharacterContext ctx, int damage) {
        ctx.reduceHealth(damage);
        // 状态转换可能依赖上下文数据:血量决定去受击还是死亡
        return ctx.getHealth() > 0 ? new HitState() : new DeadState();
    }
    // ... move、attack 略 ...
}

TCP 连接状态

TCP 协议的状态转换是状态模式的教科书级案例,也是 GoF 原书举的例子之一。连接有多个状态(LISTEN、SYN_RECEIVED、ESTABLISHED、FIN_WAIT、CLOSED),每次收到网络事件(SYN、ACK、FIN)时的响应和转换目标完全由当前状态决定。协议栈实现(如 Linux 内核的 TCP 模块)虽然是 C 语言写的,但结构上正是状态模式——每个状态一张"事件 → 响应 + 下一状态"的表。

class EstablishedState implements TcpState {
    public TcpState close(TcpContext ctx) {
        System.out.println("发送 FIN,进入 FIN_WAIT_1");
        return new FinWaitState();
    }
    public TcpState acknowledge(TcpContext ctx) {
        System.out.println("正常数据传输中的 ACK");
        return this;                        // 保持 ESTABLISHED
    }
    // 不认识的事件返回 this ...
}

工作流审批系统

审批流程中,文档在不同审批阶段有不同的审批权限:草稿 → 主管审批 → 经理审批 → 总监审批 → 通过。用状态模式建模的好处是审批阶段本身就是状态类——SupervisorApprovalManagerApproval 这些类名直接对应业务概念,产品经理看代码不用翻译。相比之下,用"字段 stage 加一堆 if (stage == 2)"的写法则完全丢失了业务语义。

class SupervisorApproval implements ApprovalState {
    public ApprovalState approve(ApprovalContext ctx, String approver) {
        System.out.println("主管 [" + approver + "] 审批通过");
        return new ManagerApproval();       // 进入下一阶段
    }
    public ApprovalState reject(ApprovalContext ctx, String approver) {
        System.out.println("主管 [" + approver + "] 驳回");
        return new DraftState();            // 退回草稿
    }
}

文档发布状态

内容管理系统里文档的生命周期天然是状态机:草稿 → 审核中 → 已审批 → 已发布。每个状态只暴露允许的操作——已发布的文档不能直接编辑(需要创建新版本)、审核中的文档不能被作者修改(防止审核过程中内容变化)。这些约束用状态模式来表达最自然,不必到处写"如果当前是审核中且用户是作者就抛异常"这样的判断。写博客发文章的后台、Confluence 之类的知识库、企业公告系统,都能套上这个结构。

class UnderReview implements DocState {
    public DocState edit(DocContext ctx) {
        System.out.println("错误:审核中不能编辑");
        return this;
    }
    public DocState approve(DocContext ctx) {
        System.out.println("文档审核通过:" + ctx.getTitle());
        return new Approved();
    }
    public DocState reject(DocContext ctx) {
        System.out.println("文档审核未通过,退回草稿");
        return new Draft();
    }
    // ... 其他不允许的操作都返回 this ...
}

Spring StateMachine 框架

手写状态类在状态不多时够用,可一旦状态超过十几个、转换规则错综复杂(审批流叠加会签、驳回、加签),自己维护状态类会越来越吃力。Spring 生态的 Spring StateMachine 正是为这种场景而生——它是 Java 领域最主流的状态机框架,把状态、事件、转换关系用配置式 API 统一描述,还内置了持久化、嵌套状态、状态机事件监听等企业级能力。下面是一个极简配置示例,StatesEvents 分别是状态与事件的枚举。

@Configuration
@EnableStateMachine
class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
        states
            .withStates()
            .initial(States.PENDING)                 // 初始状态
            .states(EnumSet.allOf(States.class))      // 全部状态
            .end(States.COMPLETED);                   // 终态
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
        transitions
            .withExternal()
            .source(States.PENDING).target(States.PAID).event(Events.PAY)      // 待支付 -> 已支付
            .and()
            .withExternal()
            .source(States.PAID).target(States.SHIPPED).event(Events.SHIP);    // 已支付 -> 已发货
    }
}

可以看到,框架里状态和转换关系是声明式配置出来的,不再需要为每个状态写一个类——source -> target + event 一行就描述一条转换。当业务状态机复杂到普通状态类难以维护时,引入 Spring StateMachine 能省下大量样板代码,这也是它和手写状态模式各擅胜场的分界线。

技术交流 & 更多原创内容,关注公众号:咖啡八杯