Java 状态机详解 – 三种状态机实现方式优雅消灭 if-else 嵌套

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 框架(企业级状态机库)进一步简化。

如果你想看更多代码细节、测试用例,或对比其他方式(如策略模式变体),告诉我你的需求,我继续展开~ 😊

文章已创建 4323

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部