Java 状态机设计:替代 if-else 的优雅架构

在后台系统开发中,状态流转无处不在:订单从 待支付 → 已支付 → 已发货,工单从 打开 → 处理中 → 解决

一开始用一堆 if/else、嵌套判断能跑通,但很快会出现:

  • 代码像洋葱层层嵌套(箭头代码,难以阅读)

  • 状态规则散落在多个分支里,难以维护与测试

  • 边界条件容易遗漏,扩展差(新增状态/事件要改核心逻辑)

所以,状态机(State Machine)诞生了——它是把状态与转移规则显式化、模块化、可测试化的解法。

状态机核心概念 

状态机三要素:

要素

含义

例子(订单系统)

状态(State)

系统所处的稳定阶段

待支付 / 已支付 / 已发货

事件(Event)

能触发状态变化的动作

支付成功 / 取消订单 / 发货

转移(Transition)

从某个状态在某个事件下到另一个状态并执行处理

待支付 + 支付成功 → 已支付(并执行支付逻辑)

状态机的常见类型:

  • 有限状态机(FSM):最常用、最简单,适合大多数业务场景。

  • 分层状态机(HSM):支持状态继承,减少重复(复杂场景)。

  • 状态图 / Statecharts:支持并发子状态、历史状态等高级特性(用于复杂流程)。

状态机实战案例

一个清晰且可扩展的状态机实现

// TransitionHandler 是一个函数式接口,可以用 lambda 实现
@FunctionalInterface
public interface TransitionHandler {
    void handle() throws Exception;
}
// 状态与事件用 enum 表示
public enum State {
    PENDING, PAID, SHIPPED, CANCELED
}
public enum Event {
    PAY_SUCCESS, CANCEL, SHIP
}
// 转移定义
public class Transition {
    public final State from;
    public final Event event;
    public final State to;
    public final TransitionHandler handler;
    public Transition(State from, Event event, State to, TransitionHandler handler) {
        this.from = from;
        this.event = event;
        this.to = to;
        this.handler = handler;
    }
}
// 简单状态机实现(遍历转移表)
import java.util.ArrayList;
import java.util.List;
public class StateMachine {
    private State current;
    private final List<Transition> transitions = new ArrayList<>();
    public StateMachine(State initial) {
        this.current = initial;
    }
    public void addTransition(State from, Event event, State to, TransitionHandler handler) {
        transitions.add(new Transition(from, event, to, handler));
    }
    public void trigger(Event event) throws Exception {
        for (Transition t : transitions) {
            if (t.from == current && t.event == event) {
                // 执行处理
                t.handler.handle();
                // 更新状态
                current = t.to;
                return;
            }
        }
        throw new IllegalStateException("非法事件[" + event + "]或当前状态[" + current + "]不支持");
    }
    public State getCurrent() {
        return current;
    }
}

使用示例(main):

public static void main(String[] args) throws Exception {
    StateMachine sm = new StateMachine(State.PENDING);
    sm.addTransition(State.PENDING, Event.PAY_SUCCESS, State.PAID, () -> {
        System.out.println("执行支付成功处理逻辑...");
    });
    sm.addTransition(State.PENDING, Event.CANCEL, State.CANCELED, () -> {
        System.out.println("执行订单取消逻辑...");
    });
    sm.addTransition(State.PAID, Event.SHIP, State.SHIPPED, () -> {
        System.out.println("执行发货逻辑...");
    });
    System.out.println("当前状态: " + sm.getCurrent());
    sm.trigger(Event.PAY_SUCCESS);
    System.out.println("当前状态: " + sm.getCurrent());
    sm.trigger(Event.SHIP);
    System.out.println("当前状态: " + sm.getCurrent());
}

输出:

当前状态: 待支付
执行支付成功处理逻辑...
当前状态: 已支付
执行发货逻辑...
当前状态: 已发货

优化:表驱动实现(查找 O(1))

原实现触发时遍历转移表,性能在大量状态/事件时受影响。把转移表做成 map[State]map[Event]*Transition,触发变成两次 map 查找,既直观又高效:

import java.util.HashMap;
import java.util.Map;
public class StateMachineV2 {
    private State current;
    private Map<State, Map<Event, Transition>> transitionMap;
    public StateMachineV2(State initial) {
        this.current = initial;
        this.transitionMap = new HashMap<>();
    }
    public void addTransition(State from, Event event, State to, TransitionHandler handler) {
        transitionMap
            .computeIfAbsent(from, k -> new HashMap<>())
            .put(event, new Transition(from, event, to, handler));
    }
    public void trigger(Event event) throws Exception {
        Map<Event, Transition> events = transitionMap.get(current);
        if (events != null) {
            Transition t = events.get(event);
            if (t != null) {
                t.handler.handle();
                current = t.to;
                return;
            }
        }
        throw new IllegalStateException("非法事件[" + event + "]或当前状态[" + current + "]不支持");
    }
    public State getCurrent() {
        return current;
    }
}

面向对象风格:状态模式

用接口把每个状态封装成一个对象,状态对象实现该状态下能做的操作。好处是逻辑分散到各自状态类,遵循单一职责;坏处是类型和样板代码增多。

public interface OrderState {
    void pay(OrderContext ctx) throws Exception;
    void cancel(OrderContext ctx) throws Exception;
    void ship(OrderContext ctx) throws Exception;
}
public class PendingState implements OrderState {
    @Override
    public void pay(OrderContext ctx) {
        System.out.println("Pending -> 支付处理");
        ctx.setState(new PaidState());
    }
    @Override
    public void cancel(OrderContext ctx) {
        System.out.println("Pending -> 取消订单");
        ctx.setState(new CanceledState());
    }
    @Override
    public void ship(OrderContext ctx) {
        throw new IllegalStateException("当前状态不能发货");
    }
}
public class PaidState implements OrderState {
    @Override
    public void pay(OrderContext ctx) {
        throw new IllegalStateException("已支付,无法重复支付");
    }
    @Override
    public void cancel(OrderContext ctx) {
        System.out.println("Paid -> 已支付取消逻辑");
        ctx.setState(new CanceledState());
    }
    @Override
    public void ship(OrderContext ctx) {
        System.out.println("Paid -> 发货处理");
        ctx.setState(new ShippedState());
    }
}
// 其余状态类略...
public class OrderContext {
    private OrderState state;
    public OrderContext(OrderState state) { this.state = state; }
    public void setState(OrderState state) { this.state = state; }
    public String getStateName() { return state.getClass().getSimpleName(); }
    public void pay() throws Exception { state.pay(this); }
    public void cancel() throws Exception { state.cancel(this); }
    public void ship() throws Exception { state.ship(this); }
}

并发与安全:

在并发场景下,必须保证状态转移是原子且幂等的:

  • 简单做法:在 Trigger 外包一层 mutex.Lock() / Unlock()

  • 更复杂:使用 DB 乐观锁(version)或分布式锁(Redis、Zookeeper)保证跨进程一致性。

  • 异步场景:用消息队列序列化事件处理,保证顺序性。


public class SafeStateMachineV2 extends StateMachineV2 {
    private final Object lock = new Object();
    public SafeStateMachineV2(State initial) { super(initial); }
    @Override
    public void trigger(Event event) throws Exception {
        synchronized (lock) {
            super.trigger(event);
        }
    }
}

持久化:

数据库里只记 order.status、事件历史(audit trail)与时间戳。代码在恢复时:从 DB 读回当前状态并注入状态机即可重建运行时行为(状态机逻辑不存入 DB)。

// 存储示例(伪代码)
orders(
  id BIGINT PRIMARY KEY,
  status VARCHAR(32),
  event_history JSON,
  version BIGINT,
  updated_at TIMESTAMP
)
// 恢复:
Order order = loadOrderFromDB(id);
StateMachineV2 sm = CreatePreconfiguredStateMachine(order.getStatusEnum());

持久化事务注意:

  • 在触发转移时,应先执行业务逻辑(可能跨系统),再持久化状态与审计(尽量保证幂等)。

  • 使用事务 + 幂等设计(或事务外补偿)保证一致性。

反模式与实践建议

别犯这些坑:

  • 过度复杂:状态超过 ~15 个,考虑拆分多个子状态机。

  • 上帝状态机:一个状态机控制太多业务,职责混乱。

  • 忽略回退:关键流程必须设计补偿/回退逻辑(尤其是跨系统流程)。

  • 缺乏监控:必须记录状态转移日志与失败告警。

监控示例(埋点):

在 trigger 前后记录日志、耗时、失败信息,监控异常率、非法事件请求。

public void triggerWithMonitoring(Event event) throws Exception {
    long start = System.currentTimeMillis();
    State old = getCurrent();
    try {
        trigger(event);
    } finally {
        System.out.printf("state transition: %s -> %s on %s, took %d ms%n",
            old, getCurrent(), event, System.currentTimeMillis() - start);
    }
}

总结

通过本文,你应该能做到:

  • 理解状态 / 事件 / 转移三要素

  • 会写一个简单可扩展的 Java 状态机

  • 知道如何做性能优化(表驱动)与并发保护

  • 理解持久化原则:只存状态,记录历史

  • 避免常见反模式,做好监控与可恢复设计

状态机不仅是代码结构,更是工程思维

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值