枚举实现简单状态机
在 Java 中,使用枚举(Enum)来实现状态机(State Machine)是一种非常优雅且高效的做法。Java 的枚举不仅是一个常量的集合,它本质上是一个闭合的类,可以拥有属性、方法,甚至可以实现接口。
最经典、最符合面向对象设计原则的实现方式是:将状态的行为抽象成方法,并在每个枚举常量中分别实现它(状态模式)。
1、业务场景:订单状态流转
我们以一个简单的"订单系统"为例,订单有三个状态:
- 待付款 (CREATED)
- 待发货 (PAID)
- 已收货 (DELIVERED)
触发状态流转的动作(事件)有:
- pay()(付款)
- ship()(发货)
2、代码实现
通过在枚举中定义抽象方法,每个状态都可以决定自己在收到某个事件时该如何跳转。
java
public enum OrderState {
// 1. 待付款状态
CREATED {
@Override
public OrderState pay() {
System.out.println("订单付款成功 -> 准备发货");
return PAID; // 流转到下一个状态
}
@Override
public OrderState ship() {
System.out.println("错误:订单尚未付款,不能发货!");
return this; // 保持原状态
}
},
// 2. 待发货状态
PAID {
@Override
public OrderState pay() {
System.out.println("提示:订单已付款,请勿重复支付。");
return this;
}
@Override
public OrderState ship() {
System.out.println("订单发货成功 -> 商品运输中");
return DELIVERED; // 流转到下一个状态
}
},
// 3. 已收货状态(终态)
DELIVERED {
@Override
public OrderState pay() {
System.out.println("错误:订单已结束,无法付款。");
return this;
}
@Override
public OrderState ship() {
System.out.println("错误:订单已结束,无法重复发货。");
return this;
}
};
// 抽象出状态机能接收的事件(动作)
public abstract OrderState pay();
public abstract OrderState ship();
}
3、上下文对象(Context)
在实际业务中,我们通常会有一个订单类(Order),它持有了当前的状态,并对外暴露统一的调用接口。
java
public class OrderContext {
private String orderId;
private OrderState state; // 持有当前状态
public OrderContext(String orderId) {
this.orderId = orderId;
this.state = OrderState.CREATED; // 初始状态为待付款
}
// 触发付款动作
public void payAction() {
// 将状态变更的控制权交给状态枚举本身
this.state = this.state.pay();
}
// 触发发货动作
public void shipAction() {
this.state = this.state.ship();
}
public OrderState getState() {
return state;
}
}
4、测试运行
java
public class StateMachineTest {
public static void main(String[] args) {
OrderContext order = new OrderContext("10001");
System.out.println("当前状态:" + order.getState()); // CREATED
// 1. 尝试直接发货(非法操作)
order.shipAction(); // 输出: 错误:订单尚未付款,不能发货!
// 2. 正常付款
order.payAction(); // 输出: 订单付款成功 -> 准备发货
System.out.println("当前状态:" + order.getState()); // PAID
// 3. 正常发货
order.shipAction(); // 输出: 订单发货成功 -> 商品运输中
System.out.println("当前状态:" + order.getState()); // DELIVERED
}
}
当前状态:CREATED
错误:订单尚未付款,不能发货!
订单付款成功 -> 准备发货
当前状态:PAID
订单发货成功 -> 商品运输中
当前状态:DELIVERED
5、总结
优点:
- 避免了密集的 if-else 或 switch-case
传统的做法通常是在一个方法里写一大堆 if (status == CREATED) { ... } else if ...。使用枚举将行为内聚到各个状态内部,代码极其清晰。 - 线程安全与单例保证
Java 的枚举实例底层是高级单例,JVM 保证了它们的线程安全和全局唯一性。 - 强类型校验
编译器会强制要求你流转后的状态必须也是 OrderState 之一,不会出现拼写错误或传入非法状态码的情况。
适用场景:
这种流转逻辑相对固定、状态数量不是特别庞大(比如10个以内)的本地轻量级状态机。如果业务极其复杂且带有大量分布式事务(如跨服务流转),则可以考虑官方的 Spring StateMachine 或阿里开源的 Cola StateMachine。