什么是状态模式(State Pattern)

状态模式允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
想象一个媒体播放器(Context 对象)。它可能有几种状态:播放中(Playing)、暂停(Paused)、停止(Stopped)。当你按下“播放/暂停”按钮时,播放器的反应完全取决于它当前是处于“播放中”还是“暂停/停止”状态。如果正在播放,它会暂停;如果已暂停或停止,它会开始播放。状态模式就是用来优雅地处理这种基于状态的行为变化的。
状态模式的核心思想是:将与特定状态相关的行为局部化,并将不同状态的行为分散到不同的状态对象中。

核心思想/步骤

  1. 定义状态接口/抽象类 (State): 定义一个接口或抽象类,用于封装与 Context 对象(需要管理状态的对象)的一个特定状态相关的行为。这个接口通常包含 Context 对象可能调用的所有方法。
  1. 创建具体状态类 (ConcreteState): 为 Context 对象的每一种可能的状态创建一个具体的类,实现 State 接口/继承抽象类。每个具体状态类实现了在该状态下,Context 对象对应方法的具体行为。
  1. 维护当前状态: Context 对象持有一个指向当前 State 对象的引用。这个引用就是 Context 的“当前状态”。
  1. 委托行为: Context 对象将所有与状态相关的请求委托给当前的 State 对象来处理。Context 本身不实现与状态相关的逻辑。
  1. 状态转换: 最关键的部分是状态转换。状态转换的逻辑可以放在:
      • Context 对象中: Context 根据某些条件或操作结果决定切换到哪个新状态。
      • State 对象中: 更常见的是,由具体状态类自身负责决定在执行完某个操作后应该转换到哪个新状态,并通过调用 Context 提供的方法(如 setState()) 来改变 Context 的当前状态引用。

优点

  1. 将状态特定的行为局部化: 每个状态的逻辑都封装在自己的类中,使得代码更加清晰、内聚,易于理解和维护。符合单一职责原则。
  1. 消除庞大的条件分支语句: 避免了在 Context 类中使用冗长的 if/else 或 switch/when 语句来根据状态判断行为,使 Context 类更简洁。
  1. 易于增加新状态: 添加一个新的状态只需要增加一个新的 ConcreteState 类,并修改相关的状态转换逻辑即可,符合开闭原则。
  1. 使状态转换显式化: 状态之间的转换逻辑被清晰地定义在 State 类或 Context 类中,更容易跟踪和管理。

可能的缺点

  1. 类数量增加: 会导致系统中类的数量增加,因为每个状态都需要一个对应的类。如果状态很少或者行为差异不大,可能会过度设计。
  1. 逻辑分散: 状态转换逻辑可能分散在多个 State 类中,如果转换逻辑复杂,可能不易于整体把握。

使用场景

  1. 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变行为时。
  1. 当一个操作中含有庞大的多分支条件语句,且这些分支依赖于该对象的状态时。通常,这些分支是与特定状态相关的。
  1. GUI 编程: UI 元素(如按钮、菜单项)的启用/禁用状态。
  1. 网络连接: 管理 TCP 连接的状态(如 Listening, Established, Closing, Closed)。
  1. 工作流引擎: 处理订单、审批流程等,其中对象(订单、申请)会在不同状态间流转(待支付、已支付、处理中、已发货、已完成、已取消)。
  1. 游戏开发: 角色的状态(站立、行走、跑步、跳跃、攻击、防御)。
  1. Android 中的场景:
      • 用户登录状态管理: 应用可以有 LoggedInState, LoggedOutState, GuestState 等,根据当前状态决定显示哪个界面、允许哪些操作。
      • 下载管理器: 文件下载任务的状态 (Pending, Downloading, Paused, Completed, Failed, Cancelled)。根据状态,下载按钮可能显示为“下载”、“暂停”、“继续”、“打开”或“重试”。
      • 蓝牙或 Wi-Fi 连接管理: 设备连接状态 (Disconnected, Connecting, Connected, Disconnecting)。UI 和可用操作会随状态变化。
      • 复杂表单/向导 (Wizard) UI: 表单的不同步骤可以看作不同的状态,控制“上一步”、“下一步”、“完成”按钮的可用性和行为。
      • 自定义 View 的交互状态: 一个复杂的自定义 View 可能有多种交互模式(如绘图工具的选择状态:画笔、橡皮擦、选择),每种模式下的触摸事件处理方式不同。

UML 图

下面是基于“媒体播放器”示例的状态模式 UML 图:
  • MediaPlayer (Context): 持有一个 PlayerState 类型的引用 currentState。它的 play(), pause(), stop() 方法会委托给 currentState 对应的同名方法。它还提供 setState() 方法供 State 对象改变其当前状态。
  • PlayerState (State): 定义了所有状态共有的接口,包含播放器能执行的操作。
  • PlayingState, PausedState, StoppedState (ConcreteState): 实现了 PlayerState 接口。每个类的 play(), pause(), stop() 方法定义了在该状态下的具体行为,并且通常在执行完动作后,调用 player.setState() 来转换到下一个状态。例如,在 PlayingState 中调用 pause() 会将播放器的状态设置为 PausedState。
  • Client: 使用 MediaPlayer 对象,调用其方法来触发行为和状态转换。

代码示例

在这个例子中:
  • MediaPlayer 是 Context,它不知道具体的播放逻辑,只知道委托给 currentState。
  • PlayerState 是状态接口。
  • PlayingState, PausedState, StoppedState 是具体状态类,它们实现了接口方法。注意,在每个方法内部,它们不仅执行了该状态下的动作(打印信息),还通过调用 player.setState() 来控制状态的转换。
  • 我们使用了 Kotlin 的 object 来创建单例的具体状态类,因为这些状态本身不需要维护实例变量,可以共享同一个实例。
  • 客户端代码通过调用 player 的 play(), pause(), stop() 方法来与其交互,可以看到播放器的行为和状态根据调用的方法和当前状态动态变化。

总结

  • 状态模式通过将与状态相关的行为封装到不同的状态对象中,使得对象可以在其内部状态改变时,平滑地切换行为
  • 它有效地替代了复杂的条件判断语句,提高了代码的可维护性和可扩展性。
  • 在 Android 开发中,对于管理具有明确状态转换和状态依赖行为的组件(如连接、任务、UI 模式)非常有用。
💡
记住它的核心:行为委托给当前状态对象,状态对象负责处理行为并管理状态转换
设计模式——行为型-命令模式设计模式——行为型-模版方法模式
Loading...