用抽象类实现状态机的核心是将状态封装为可替换的行为容器,通过抽象类定义统一操作契约、具体子类专注合法动作、上下文仅负责委托与状态托管,从而解耦判断、执行与流转。

用抽象类实现状态机,核心是把“状态”变成可替换的行为容器,而不是一堆散落在方法里的 if-else。关键不在于抽象类本身多高级,而在于它如何把状态判断、行为执行、流转控制三件事拆清楚、管到位。
抽象状态类定义统一契约
抽象类(或接口)要声明所有可能被触发的操作,比如 pay()、ship()、cancel(),但不写具体逻辑。它只说“每个状态都得支持这些动作”,至于怎么做,交给子类决定。这样上下文(如 OrderContext)调用时完全不用关心当前是什么状态,直接委托即可。
- 推荐用抽象类而非接口,方便后续统一注入日志、审计、状态校验等横切逻辑
- 方法签名里建议传入上下文对象(如 void pay(OrderContext context)),便于状态类内部修改 context 的当前状态
- 可以加一个默认的 invalidOperation() 方法,让非法操作有统一兜底行为
具体状态类专注本职逻辑
每个具体状态类(如 PendingPaymentState、PaidState)继承抽象类,只实现自己合法的动作。比如待支付状态只允许 pay(),其他操作一律抛异常或走 invalidOperation();已支付状态才允许 ship(),且 ship() 执行完必须主动调用 context.setState(new ShippedState()) 完成流转。
- 状态类内部不保存业务数据,只处理规则——数据全在上下文里,保持轻量
- 状态转换不依赖外部条件判断,而是由当前状态类自己决定下一步该切到谁,职责清晰
- 新增状态只需加一个新类,不用动已有代码,符合开闭原则
上下文类做状态托管与委托中枢
上下文类(如 OrderContext)持有当前状态引用,并提供统一入口方法(如 handlePay())。它不参与任何业务判断,只是把请求原样转给当前状态对象。初始化时指定首个状态,之后所有状态变更都由具体状态类驱动。
- 避免在上下文中写 switch 或 if 判断状态再分发——那是倒退
- 状态字段建议设为 final 类型(如 State),靠 setter 替换引用,防止意外篡改
- 可加一层保护:在 setState 前校验是否允许从旧状态跳转到新状态(可选增强)
实战中容易忽略的细节
真正落地时,几个小点常决定成败:
- 状态类之间不要互相引用,避免循环依赖;所有跳转都通过上下文间接完成
- 数据库持久化时,只存状态枚举值(如 "PAID"),加载时根据值 new 对应状态类并注入 context
- 测试重点不是单个方法,而是“在 A 状态下调 B 操作 → 是否进入 C 状态 + 是否触发预期副作用”
- 如果某状态逻辑特别重(比如含异步回调、事务边界),可在该具体状态类里封装完整流程,不污染上下文

















