Design-Pattern-State

状态模式是一种行为设计模式,它允许对象在其内部状态改变时改变它的行为,对象看起来好像修改了它的类。

场景

笔者曾基于按键精灵开发过一些自动化软件,用于在PC端及移动设备上模拟用户点击与输入操作,从而自动完成游戏内的日常任务。其底层实现逻辑为:程序通过一个持续运行的死循环,定时截取当前屏幕画面,基于图像特征判断当前所处的游戏界面状态(如主菜单、战斗中、战斗结束等),随后依据预先配置的操作策略,执行对应的自动化动作——例如当识别到主菜单界面时,触发“开始战斗”按钮的点击操作;当检测到战斗结束界面时,则自动完成“结算”操作。在具体代码实现层面,这一系列界面状态判断与操作分发的逻辑,最初是通过传统的if条件分支或switch语句结构来完成的。

对于上述过程,其核心设计思想在于:程序在运行过程中始终仅处于若干种预定义的有限状态之一。针对每一种特定的状态,程序会展现出差异化的行为逻辑,并且能够在无需中间过渡过程的情况下,直接从一个状态切换至另一个状态。然而,状态的变更并非必然发生——程序可能基于当前状态触发向其他状态的跳转,也可能保持当前状态持续运行。这些预先设定且数量有限的、定义了”在何种状态下应如何转换至其他状态(或维持原状态)“的规则,被统称为状态转移(State Transition。这类程序可以抽象为有限状态机。显然,有限状态机由有限个状态、状态下的行为、状态转移规则所定义。

随着向自动化软件添加了更多状态和行为,整个程序变得难以维护,原本简洁的if-elseswitch-case会膨胀为冗长的嵌套分支。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (currentScreen.equals(MAIN_MENU)) {
clickStartBattle();
} else if (currentScreen.equals(BATTLE_FIELD)) {
if (battlePhase.equals(BATTLE_ONGOING)) {
checkHpAndUseSkill();
} else if (battlePhase.equals(BATTLE_ENDED)) {
clickSettlement();
}
} else if (currentScreen.equals(INVENTORY)) {
// 使用switch判断选中物品
switch (selectedItem) {
case ITEM_POTION:
usePotion();
break;
case ITEM_EQUIPMENT:
equipItem();
break;
default:
System.out.println("未识别的物品选择");
}
}

状态模式

状态模式的核心思想是将对象的行为与其状态解耦,通过以下方式实现:

  1. 状态抽象化:为对象的每个可能状态创建一个独立的类,将原本分散在if/switch分支中的状态相关行为抽取到对应的状态类中。每个状态类负责封装该状态下对象应表现的行为逻辑。
  2. 上下文委托:原始对象(称为上下文Context)不再自行处理所有状态相关的行为,而是:
    • 持有一个表示当前状态的状态对象引用
    • 将所有状态相关的操作委托给当前状态对象处理
    • 提供状态切换接口供状态对象修改当前状态
  3. 状态转换机制:状态对象在执行行为逻辑后,可以:
    • 保持当前状态不变(当无需切换时)
    • 通过上下文切换到其他状态(当满足转换条件时)

显然需要定义一个状态接口,使得上下文可以方便的引用和切换不同状态。状态接口中需要声明哪些方法,通常取决于具体的业务场景以及上下文对象的行为变化。例如上文中的自动化软件的例子,程序仅根据所处状态决定执行逻辑,而不涉及复杂的业务操作。那可以仅简单声明一个handle方法。

然而,在更复杂的业务系统中,比如订单系统。订单有几种状态:待支付、已支付、已发货、已完成等。每种状态下,对某些操作(如支付、取消、发货)的响应是不同的。此时仅通过状态无法确定执行逻辑,还需要额外根据每一种可能的业务操作。因此需要在状态接口中声明pay()cancel()ship()等方法。

至此,有限个状态、状态下的行为已经定义完毕,还需要实现状态转移规则。每个具体状态中,需要根据执行逻辑的结果,切换到下一个状态,或继续保持当前状态。要在状态类中切换上下文的状态,那么需要上下文类需要提供public setter。而状态类如何引用上下文对象,有两个策略:通过构造函数和成员变量引用、通过方法的入参引用。如果通过成员变量引用,意味着状态对象和某个具体上下文对象绑定了,无法构建上下文和状态多对多的关系,也不利于复用状态对象。所以笔者还是更建议通过方法入参传递上下文对象引用。

基于上述描述,可以给出如下类图。

classDiagram
    direction LR
    class Context {
        -state: State
        +Context(initialState: State)
        +setState(state: State)
        +request()
    }

    class State {
        <<interface>>
        +handle(context: Context)
    }

    class ConcreteState {
        +handle(context: Context)
    }
    
    class Client {
    }

    Context --o State : 维护当前状态
    State <|-- ConcreteState
    Context <-- ConcreteState
    Client --> Context
    Client --> ConcreteState

总结

当代码中出现复杂的if/switch语句,可以考虑使用状态模式。

状态模式中,代码中的不同if/switch分支被抽取为状态类。在原上下文对象中通过引用和切换状态对象,来表现出不同行为。

每个状态类中,需要定义具体的执行逻辑,并根据执行结果决定下一个状态。


Design-Pattern-State
http://dracoyus.github.io/2025/11/07/Design-Pattern-State/
作者
DracoYu
发布于
2025年11月7日
许可协议