首页 > 编程语言 >状态模式与开闭原则实现可扩展有限状态机

状态模式与开闭原则实现可扩展有限状态机

来源:互联网 2026-06-30 08:06:00

状态模式的经典实现里,最让人头疼的问题就是:新增一个状态,得把周围一堆状态类的代码翻个底朝天。本文要聊的方案,恰恰把这层转换逻辑塞回状态自己手里——你只管加新类,旧代码一动都不用动。 核心思路:状态自治,上下文只做调度 先亮出核心思路:把“状态要切换到哪儿”的决定权,完整交给每个状态类自身。这样,上

状态模式的经典实现里,最让人头疼的问题就是:新增一个状态,得把周围一堆状态类的代码翻个底朝天。本文要聊的方案,恰恰把这层转换逻辑塞回状态自己手里——你只管加新类,旧代码一动都不用动。

核心思路:状态自治,上下文只做调度

先亮出核心思路:把“状态要切换到哪儿”的决定权,完整交给每个状态类自身。这样,上下文(比如那个管理密码的SecretKeeper)就只需要做三件事——接收输入、让当前状态干活、问一句“该切换了没?切到谁?”至于具体切到哪个新状态,那是每个状态自己的私事。

传统写法的痛点:高耦合,难扩展

传统写法里呢?状态间的跳转通常散落在各个State的evKey()方法里,直接硬编码sk.setState(new XxxState())。这等于把“谁可以跳到谁”写死在了每个状态内部,每次新增一个状态——比如半锁定态SemiLockedState——Locked和Unlocked里可能都要加个分支判断。高耦合、低扩展,改一处牵全身,这恰恰是开闭原则最不待见的样子。

长期稳定更新的攒劲资源: >>>点此立即查看<<<

理想解法:拆分“时机”与“目标”,让状态自己说话

理想的解法其实很直白:把“什么时候切”和“切到哪儿”拆开,让状态自己说话。具体做三件事:

  • 上下文只做调度:收到输入 → 更新当前状态的内部数据 → 触发状态行为 → 询问“是否该切?切到哪个状态?”
  • 每个状态类独立封装三件事:数据管理(比如密码怎么累计)、业务行为(比如打印密钥)、转换策略(返回下一个状态实例)。
  • 状态转换用getSwitchedState()显式声明,而不是硬编码new XxxState()。这样未来配合工厂或策略动态生成也非常自然。

重构代码骨架:SecretKeeper的简化版本

下面是对应的重构代码骨架,不妨先看一眼SecretKeeper的简化版本:

public class SecretKeeper {
    private int secretCode;
    private String secret1, secret2;
    private State state;

    public SecretKeeper(int secretCode, String secret1, String secret2) {
        this.secretCode = secretCode;
        this.secret1 = secret1;
        this.secret2 = secret2;
        this.state = new LockedState(); // 初始状态
    }

    void printSecret1() { System.out.println(secret1); }
    void printSecret2() { System.out.println(secret2); }
    boolean checkCode(int code) { return code == secretCode; }

    void evKey(int digit) {
        state.setCode(digit);          // 输入处理(状态内部逻辑)
        state.doStuff();               // 执行当前状态行为(如输出、校验等)
        if (checkCode(state.getCode())) {
            state = state.getSwitchedState(); // 由状态自身决定下一状态
        }
    }
}

关键接口与实现:State及子类

关键在State接口和实现类:

public interface State {
    void setCode(int digit);
    int getCode();
    State getSwitchedState(); //  开闭关键:新增状态时,只需实现此方法,不改其他类
    void doStuff();
}

public class LockedState implements State {
    private int code = 0;

    @Override
    public void setCode(int digit) {
        if (digit == 0) code = 0;
        else code = code * 10 + digit;
    }

    @Override
    public int getCode() { return code; }

    @Override
    public State getSwitchedState() {
        return new SemiLockedState(); //  新增 SemiLocked?仅在此处修改一行
    }

    @Override
    public void doStuff() {
        // 当前无副作用,仅累积输入
    }
}

public class SemiLockedState implements State {
    @Override
    public void setCode(int digit) { /* 可定义新规则,如二次确认 */ }

    @Override
    public int getCode() { return 0; }

    @Override
    public State getSwitchedState() {
        return new UnlockedState(); // 或返回 LockedState,完全由该状态控制
    }

    @Override
    public void doStuff() {
        System.out.println("Semi-locked: awaiting confirmation...");
    }
}

这套设计带来的好处

这套设计带来的好处,值得掰扯几下:

  • 符合开闭原则:添加SemiLockedState时,只用新建类实现State接口,无需动SecretKeeper、LockedState、UnlockedState的任何一行代码
  • 职责一刀切清楚:每个状态类自己管数据、行为和流转,上下文就是个干净的调度器。
  • 测试友好:状态行为可以独立单元测试,不用费劲搭上下文生命周期。
  • 扩展弹性大getSwitchedState()可以返回工厂创建的实例、缓存单例,甚至基于条件返回不同状态,未来要支持配置化状态机也不难。

落地注意事项

当然,落地时也得留点神:

  • getSwitchedState()里别塞太多业务逻辑(比如数据库查询),保持轻量。如果外部依赖避不开,通过构造注入传进去。
  • 状态间共享数据(比如全局计数器)时,稳妥的做法是让SecretKeeper提供只读接口,而不是让状态直接持有上下文引用,避免循环依赖。
  • 状态机规模一大,考虑引入状态注册表(比如Map>),配合字符串驱动实现配置化切换,维护成本会低很多。

总结:可演进、好维护的状态机架构

总结一句话:这种设计不只是解决了“改一个状态要动一堆类”的痛点,更重要的是给出了一个真正可演进、好维护的有限状态机架构。下次你写状态机,不妨试试让状态自己决定下一步去哪。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。