在日常的开发中,我们经常会处理一个流程的各种状态,代码中充斥着大量关于状态的if-else,今天我们来认识一个东西,使你更靠近优雅代码。

概念

状态机(State Machine),全称有限状态机(Finite State Machine, FSM),是计算机科学中一个非常核心的概念。简单来说,它是一个数学模型,用来表示一个对象在其生命周期内所经历的各种状态,以及它是如何根据事件从一个状态切换到另一个状态的。

核心要素

要理解状态机,记住这四个关键词:

  • State(状态):对象所处的情况(如:关灯、开灯)。
  • Event(事件/触发):导致状态改变的外部输入(如:按了一下开关)。
  • Transition(转换):从一个状态切换到另一个状态的动作。
  • Action(动作):转换发生时执行的具体逻辑。

例子

自动售货机

自动售货机是状态机的完美体现。我们可以将其简化为以下几个状态:

  • 待机(Idle):等待投币。
  • 已投币(Has Money):等待选择商品。
  • 出货中(Dispensing):正在掉落商品。
  • 找零(Returning Change):完成交易。

运作流程:

  • 场景 A:你在“待机”状态投了 5 块钱(事件),售货机进入“已投币”状态。
  • 场景 B:你在“已投币”状态点击了可乐(事件),机器开始扣款并进入“出货中”状态。
  • 约束性:如果你在“待机”状态直接点可乐,状态机没有定义这个“事件”对应的“转换”,所以机器不会有反应。这就是状态机的魅力——它通过硬性的逻辑约束,保证了系统的鲁棒性。

Thread 状态转换

Java 的 Thread 类本身就是一个标准的状态机应用。

  • 状态:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED。
  • 转换:调用 start() 从 NEW 变 RUNNABLE;调用 wait() 进入 WAITING。

为什么要用状态机?

如果你在写代码时发现:

  • 存在大量的全局标志位(isPaid, isStarted, isFinished)。
  • if-else 逻辑深不见底,修改一处经常导致其他状态逻辑崩坏。

那么,你就该考虑状态机模式了。它能将复杂的逻辑解耦,变成一张清晰的“状态拓扑图”,让代码从“面向过程”升华为“面向数学模型”。

代码

我们以自动售货机为例,演示下状态机的使用。

  1. 首先,我们定义一个接口,规定所有状态必须具备的动作(事件)。
public interface State {
    void insertMoney();    // 投币
    void selectProduct();  // 选择商品
    void dispense();       // 出货
    void returnChange();   // 找零
}
  1. 实现具体状态类
    每个类代表售货机的一个特定状态,并决定在该状态下触发事件会发生什么。
// 1. 待机状态
class IdleState implements State {
    private VendingMachine machine;

    public IdleState(VendingMachine machine) { this.machine = machine; }

    @Override
    public void insertMoney() {
        System.out.println("已投币,请选择商品。");
        machine.setState(machine.getHasMoneyState());
    }

    @Override
    public void selectProduct() { System.out.println("请先投币!"); }

    @Override
    public void dispense() { System.out.println("请先投币并选择商品!"); }

    @Override
    public void returnChange() { System.out.println("未投币,无需找零。"); }
}

// 2. 已投币状态
class HasMoneyState implements State {
    private VendingMachine machine;

    public HasMoneyState(VendingMachine machine) { this.machine = machine; }

    @Override
    public void insertMoney() { System.out.println("已投币,无需重复投币。"); }

    @Override
    public void selectProduct() {
        System.out.println("商品已选择,正在处理...");
        machine.setState(machine.getDispensingState());
        machine.dispense(); // 自动进入出货流程
    }

    @Override
    public void dispense() { System.out.println("请先选择商品!"); }

    @Override
    public void returnChange() {
        System.out.println("退币成功。");
        machine.setState(machine.getIdleState());
    }
}

// 3. 出货中状态
class DispensingState implements State {
    private VendingMachine machine;

    public DispensingState(VendingMachine machine) { this.machine = machine; }

    @Override
    public void insertMoney() { System.out.println("请稍候,正在出货..."); }

    @Override
    public void selectProduct() { System.out.println("请稍候,正在出货..."); }

    @Override
    public void dispense() {
        System.out.println("商品已掉落,请领取。");
        machine.setState(machine.getReturningChangeState());
        machine.returnChange(); // 自动进入找零流程
    }

    @Override
    public void returnChange() { System.out.println("正在出货,请稍后找零。"); }
}

// 4. 找零状态
class ReturningChangeState implements State {
    private VendingMachine machine;

    public ReturningChangeState(VendingMachine machine) { this.machine = machine; }

    @Override
    public void insertMoney() { System.out.println("找零中,请勿投币。"); }

    @Override
    public void selectProduct() { System.out.println("找零中,无法选择商品。"); }

    @Override
    public void dispense() { System.out.println("已出货。"); }

    @Override
    public void returnChange() {
        System.out.println("找零完毕,感谢使用。");
        machine.setState(machine.getIdleState());
    }
}
  1. 环境上下文(VendingMachine)
    这是状态机的持有者,负责维护当前状态并对外暴露接口。
public class VendingMachine {
    private State idleState;
    private State hasMoneyState;
    private State dispensingState;
    private State returningChangeState;

    private State currentState; // 当前状态

    public VendingMachine() {
        idleState = new IdleState(this);
        hasMoneyState = new HasMoneyState(this);
        dispensingState = new DispensingState(this);
        returningChangeState = new ReturningChangeState(this);
        
        currentState = idleState; // 初始状态为待机
    }

    // 设置新状态
    public void setState(State state) { this.currentState = state; }

    // 暴露给用户的操作
    public void insertMoney() { currentState.insertMoney(); }
    public void selectProduct() { currentState.selectProduct(); }
    public void dispense() { currentState.dispense(); }
    public void returnChange() { currentState.returnChange(); }

    // Getter方法用于状态转换
    public State getIdleState() { return idleState; }
    public State getHasMoneyState() { return hasMoneyState; }
    public State getDispensingState() { return dispensingState; }
    public State getReturningChangeState() { return returningChangeState; }
}
  1. 运行演示
public class Main {
    public static void main(String[] args) {
        VendingMachine machine = new VendingMachine();

        System.out.println("--- 场景1:正常购买 ---");
        machine.insertMoney();   // 待机 -> 已投币
        machine.selectProduct(); // 已投币 -> 出货中 -> 找零 -> 待机

        System.out.println("\n--- 场景2:非法操作 ---");
        machine.selectProduct(); // 此时是待机状态,会提示“请先投币”
        
        System.out.println("\n--- 场景3:投币后反悔 ---");
        machine.insertMoney();
        machine.returnChange();  // 已投币 -> 待机
    }
}

总结:

解耦:售货机类(VendingMachine)不再需要处理复杂的逻辑判断,它只需要把请求“委派”给当前状态对象。

鲁棒性:如果你想在“待机”时执行“出货”,代码逻辑被分散在 IdleState 类中,你只需要在那写一句提示语,系统永远不会进入非法状态。

可扩展性:如果以后增加了“维修中(Maintenance)”状态,你只需要新建一个类并修改少量的转换逻辑,而不需要动原来的长篇大论的 switch 语句。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐