Java 状态机详解:三种实现方式优雅消灭 if-else 嵌套
在 Java 开发中,状态机(Finite State Machine,FSM)是一种经典的设计模式,用于管理对象的有限状态和状态之间的转换。它特别适合处理复杂业务逻辑,比如订单流程(待支付 → 已支付 → 发货 → 收货)、用户认证(未登录 → 登录中 → 已登录)、游戏角色状态(idle → running → jumping)等。
为什么需要状态机?
传统的 if-else 嵌套容易导致代码膨胀、难以维护(“意大利面代码”),尤其当状态和事件增多时。状态机通过将状态和转换逻辑解耦,能让代码更清晰、可扩展、可测试。
下面,我们先看一个 if-else 嵌套的“坏”例子(订单状态机),然后介绍三种优雅实现方式。每种方式都附带完整代码示例(基于 Java 8+,可直接复制运行)。这些方式都能“消灭” if-else 嵌套,但侧重点不同。
问题示例:if-else 嵌套的“坏”代码
假设一个订单系统,有状态:PENDING(待支付)、PAID(已支付)、SHIPPED(已发货)、DELIVERED(已收货)。事件:支付、发货、确认收货。
public class Order {
private String state = "PENDING"; // 初始状态
public void pay() {
if ("PENDING".equals(state)) {
state = "PAID";
System.out.println("支付成功,状态变为 PAID");
} else if ("PAID".equals(state)) {
System.out.println("已支付,无需重复");
} else {
System.out.println("无效操作");
}
}
public void ship() {
if ("PAID".equals(state)) {
state = "SHIPPED";
System.out.println("发货成功,状态变为 SHIPPED");
} else if ("PENDING".equals(state)) {
System.out.println("请先支付");
} else {
System.out.println("无效操作");
}
}
public void deliver() {
if ("SHIPPED".equals(state)) {
state = "DELIVERED";
System.out.println("收货成功,状态变为 DELIVERED");
} else {
System.out.println("无效操作");
}
}
public static void main(String[] args) {
Order order = new Order();
order.pay(); // 支付成功
order.ship(); // 发货成功
order.deliver(); // 收货成功
}
}
问题:每个方法都有一堆 if-else;新增状态/事件时,所有方法都要改;容易出错、难以扩展。
方式一:枚举 + switch-case(最简单、轻量级)
使用枚举定义状态和事件,在一个方法中用 switch 处理所有转换。适合状态不多(<10个)的简单场景。
优点:代码集中、易理解、无需额外类。
缺点:switch 块可能变长;不适合复杂动作(每个 case 只适合简单逻辑)。
适用:小项目、快速原型。
public class OrderEnum {
enum State { PENDING, PAID, SHIPPED, DELIVERED }
enum Event { PAY, SHIP, DELIVER }
private State currentState = State.PENDING;
public void handleEvent(Event event) {
switch (currentState) {
case PENDING:
if (event == Event.PAY) {
currentState = State.PAID;
System.out.println("支付成功,状态变为 PAID");
} else {
System.out.println("无效操作");
}
break;
case PAID:
if (event == Event.SHIP) {
currentState = State.SHIPPED;
System.out.println("发货成功,状态变为 SHIPPED");
} else {
System.out.println("无效操作");
}
break;
case SHIPPED:
if (event == Event.DELIVER) {
currentState = State.DELIVERED;
System.out.println("收货成功,状态变为 DELIVERED");
} else {
System.out.println("无效操作");
}
break;
case DELIVERED:
System.out.println("订单已完成,无操作");
break;
default:
System.out.println("未知状态");
}
}
public static void main(String[] args) {
OrderEnum order = new OrderEnum();
order.handleEvent(Event.PAY); // 支付成功
order.handleEvent(Event.SHIP); // 发货成功
order.handleEvent(Event.DELIVER); // 收货成功
}
}
扩展提示:如果动作复杂,可以在 case 中调用私有方法执行具体逻辑。
方式二:状态模式(State Pattern,经典 OOP 方式)
使用接口定义状态行为,每个状态一个实现类。订单类持有一个状态对象,根据事件委托给当前状态处理。
优点:每个状态独立类,易扩展(新增状态只需加类);符合开闭原则(修改关闭,扩展开放)。
缺点:类爆炸(状态多时类文件多);初始代码量大。
适用:中等复杂场景,企业级系统。
// 状态接口
interface OrderState {
void pay(OrderContext context);
void ship(OrderContext context);
void deliver(OrderContext context);
}
// 上下文类(订单)
class OrderContext {
private OrderState currentState;
public OrderContext() {
currentState = new PendingState(); // 初始状态
}
public void setState(OrderState state) {
this.currentState = state;
}
public void pay() { currentState.pay(this); }
public void ship() { currentState.ship(this); }
public void deliver() { currentState.deliver(this); }
}
// 具体状态类(Pending)
class PendingState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("支付成功,状态变为 PAID");
context.setState(new PaidState());
}
@Override
public void ship(OrderContext context) {
System.out.println("请先支付");
}
@Override
public void deliver(OrderContext context) {
System.out.println("无效操作");
}
}
// PaidState(类似,其他状态类省略)
class PaidState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("已支付,无需重复");
}
@Override
public void ship(OrderContext context) {
System.out.println("发货成功,状态变为 SHIPPED");
context.setState(new ShippedState());
}
@Override
public void deliver(OrderContext context) {
System.out.println("无效操作");
}
}
// ShippedState 和 DeliveredState 类似...
public class OrderStatePattern {
public static void main(String[] args) {
OrderContext order = new OrderContext();
order.pay(); // 支付成功
order.ship(); // 发货成功
order.deliver(); // 无效操作(需实现 ShippedState)
}
}
扩展提示:每个状态类可以持有上下文数据;用枚举管理状态类实例(单例)。
方式三:表驱动法(使用 Map 的策略模式)
用 Map 映射“当前状态 + 事件”到“下一个状态 + 动作”。适合状态转换规则明确的场景。
优点:配置化、易修改(Map 可以从配置文件加载);无 switch,无多类。
缺点:动作复杂时需用 Lambda 或函数接口;可读性稍差。
适用:规则多、需动态配置的系统(如游戏 AI、流程引擎)。
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class OrderTableDriven {
enum State { PENDING, PAID, SHIPPED, DELIVERED }
enum Event { PAY, SHIP, DELIVER }
private State currentState = State.PENDING;
// 动作接口(Consumer 消费上下文,这里简化无上下文)
private static class Transition {
State nextState;
Consumer<Void> action;
Transition(State next, Consumer<Void> act) {
nextState = next;
action = act;
}
}
private final Map<State, Map<Event, Transition>> stateMachine = new HashMap<>();
public OrderTableDriven() {
// 配置状态机表
Map<Event, Transition> pendingMap = new HashMap<>();
pendingMap.put(Event.PAY, new Transition(State.PAID, v -> System.out.println("支付成功")));
stateMachine.put(State.PENDING, pendingMap);
Map<Event, Transition> paidMap = new HashMap<>();
paidMap.put(Event.SHIP, new Transition(State.SHIPPED, v -> System.out.println("发货成功")));
stateMachine.put(State.PAID, paidMap);
Map<Event, Transition> shippedMap = new HashMap<>();
shippedMap.put(Event.DELIVER, new Transition(State.DELIVERED, v -> System.out.println("收货成功")));
stateMachine.put(State.SHIPPED, shippedMap);
// DELIVERED 无转换
stateMachine.put(State.DELIVERED, new HashMap<>());
}
public void handleEvent(Event event) {
Map<Event, Transition> transitions = stateMachine.get(currentState);
if (transitions == null || !transitions.containsKey(event)) {
System.out.println("无效操作");
return;
}
Transition trans = transitions.get(event);
trans.action.accept(null); // 执行动作
currentState = trans.nextState;
}
public static void main(String[] args) {
OrderTableDriven order = new OrderTableDriven();
order.handleEvent(Event.PAY); // 支付成功
order.handleEvent(Event.SHIP); // 发货成功
order.handleEvent(Event.DELIVER); // 收货成功
}
}
扩展提示:动作可以用 Function 或 Runnable;Map 可以从 JSON/YAML 加载,实现配置驱动。
总结与选择建议
- 方式一(枚举 + switch):入门级,适合小状态机(<5状态)。
- 方式二(状态模式):中高级,适合复杂动作(每个状态有独立逻辑)。
- 方式三(表驱动):高级,适合规则多、需配置化的系统。
三种方式都比 if-else 优雅,选择基于项目复杂度。实际开发中,可结合 Spring StateMachine 框架(企业级状态机库)进一步简化。
如果你想看更多代码细节、测试用例,或对比其他方式(如策略模式变体),告诉我你的需求,我继续展开~ 😊