状态模式 发表于 2025-07-16 更新于 2025-07-16
设计模式 状态模式 Rif 2025-07-16 2025-07-16 设计模式笔记:状态模式 (State Pattern) 一、一句话概括 允许一个对象在其内部状态 改变时改变它的行为 ,这个对象看起来就像是改变了它的类。
二、为什么需要它 (Why - The Pain Point) 想象一下,你正在为一个糖果售卖机编写控制程序。一个售卖机有多种状态:没投币(NoCoinState) 、有投币(HasCoinState) 、糖果售罄(SoldOutState) 、售出糖果(SoldState) 。
对于用户的每个动作(insertCoin(), turnCrank()),售卖机的响应完全取决于它当前处于哪个状态。
糟糕的设计(使用条件判断):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class GumballMachine { final static int SOLD_OUT = 0 ; final static int NO_COIN = 1 ; final static int HAS_COIN = 2 ; final static int SOLD = 3 ; int state = SOLD_OUT; int count = 0 ; public void insertCoin () { if (state == HAS_COIN) { System.out.println("You can't insert another coin" ); } else if (state == NO_COIN) { state = HAS_COIN; System.out.println("You inserted a coin" ); } else if (state == SOLD_OUT) { System.out.println("You can't insert a coin, the machine is sold out" ); } else if (state == SOLD) { System.out.println("Please wait, we're already giving you a gumball" ); } } }
这种设计的弊端非常明显:
代码难以阅读和维护 :大量的条件判断语句交织在一起,逻辑混乱,难以理解。
违反开闭原则 :如果需要增加一个新的状态(例如“维护中状态”),你必须修改所有方法,在每个 if-else 结构中添加新的分支。这使得代码非常脆弱。
状态转换逻辑分散 :状态转换的逻辑(state = HAS_COIN;)散落在各个方法的条件分支中,而不是集中管理。
核心问题 :如何优雅地管理一个拥有复杂状态流的对象,避免使用庞大的条件语句,并使状态的扩展变得容易?
三、它是什么 (What - The Solution) 状态模式提出了一种绝佳的解决方案:将每一种状态的行为封装到各自独立的类中 。
它让一个“上下文”对象(Context,即糖果机)持有一个对“状态”对象(State)的引用。当请求到达时,上下文对象将请求委托给当前的状态对象来处理。状态对象不仅负责执行动作,还负责决定在动作完成后,上下文应该转换到哪个新状态。
核心思想和角色:
**上下文 (Context)**:
就是那个拥有状态的对象,例如 GumballMachine。
它维护一个对当前状态对象(ConcreteState)的引用。
它将所有与状态相关的请求都委托给当前的状态对象。
它提供一个 setter 方法,供状态对象在需要时改变上下文的当前状态。
**状态接口 (State)**:
定义了一个接口,用来封装与上下文特定状态相关的行为。
接口中通常包含对应于用户操作的方法,如 insertCoin(), turnCrank()。
**具体状态 (Concrete State)**:
实现了 State 接口的类,每一个类代表一种具体的状态。
它实现了在该状态下,对于各种用户操作应有的行为。
它负责在适当的时候,将上下文对象的状态切换到下一个状态。
状态模式 vs. 策略模式 这两个模式的UML图几乎一模一样,但意图完全不同:
状态模式 :关注的是对象在不同状态下的行为变化 和自动的状态转移 。状态的改变通常由状态对象自身或上下文内部驱动。
策略模式 :关注的是为对象提供可互换的算法 ,而这些算法的选择通常由客户端 在外部决定。上下文本身不关心状态转移。
四、怎么做 (How - The Blueprint) 基本步骤(以糖果机为例):
创建 State 接口,定义所有可能的操作:insertCoin(), ejectCoin(), turnCrank(), dispense()。
为每个状态创建一个具体状态类(NoCoinState, HasCoinState 等),实现 State 接口。每个类都需要持有对 GumballMachine(Context)的引用,以便能够改变其状态。
创建 GumballMachine(Context)类。
持有所有可能的状态对象的实例。
持有一个 currentState 变量,引用当前的状态。
将所有操作(insertCoin() 等)委托给 currentState 的同名方法。
提供一个 setState(State state) 方法。
在 GumballMachine 的构造函数中,初始化所有状态对象,并设置初始状态。
Java 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 interface State { void insertCoin () ; void ejectCoin () ; void turnCrank () ; void dispense () ; } class GumballMachine { private State soldOutState; private State noCoinState; private State hasCoinState; private State soldState; private State currentState; private int count = 0 ; public GumballMachine (int numberGumballs) { soldOutState = new SoldOutState (this ); noCoinState = new NoCoinState (this ); hasCoinState = new HasCoinState (this ); soldState = new SoldState (this ); this .count = numberGumballs; if (numberGumballs > 0 ) { currentState = noCoinState; } else { currentState = soldOutState; } } public void insertCoin () { currentState.insertCoin(); } public void ejectCoin () { currentState.ejectCoin(); } public void turnCrank () { currentState.turnCrank(); currentState.dispense(); } void setState (State state) { this .currentState = state; } public State getSoldOutState () { return soldOutState; } public State getNoCoinState () { return noCoinState; } public State getHasCoinState () { return hasCoinState; } public State getSoldState () { return soldState; } public int getCount () { return count; } public void releaseBall () { System.out.println("A gumball comes rolling out the slot..." ); if (count > 0 ) { count = count - 1 ; } } } class HasCoinState implements State { GumballMachine gumballMachine; public HasCoinState (GumballMachine gumballMachine) { this .gumballMachine = gumballMachine; } @Override public void insertCoin () { System.out.println("You can't insert another coin" ); } @Override public void ejectCoin () { System.out.println("Coin returned" ); gumballMachine.setState(gumballMachine.getNoCoinState()); } @Override public void turnCrank () { System.out.println("You turned..." ); gumballMachine.setState(gumballMachine.getSoldState()); } @Override public void dispense () { System.out.println("No gumball dispensed" ); } } class NoCoinState implements State { GumballMachine machine; public NoCoinState (GumballMachine m) { this .machine = m; } public void insertCoin () { System.out.println("You inserted a coin" ); machine.setState(machine.getHasCoinState()); } public void ejectCoin () { System.out.println("You haven't inserted a coin" ); } public void turnCrank () { System.out.println("You turned, but there's no coin" ); } public void dispense () { System.out.println("You need to pay first" ); } } public class StatePatternDemo { public static void main (String[] args) { GumballMachine gumballMachine = new GumballMachine (5 ); System.out.println(gumballMachine); gumballMachine.insertCoin(); gumballMachine.turnCrank(); System.out.println(gumballMachine); gumballMachine.insertCoin(); gumballMachine.ejectCoin(); gumballMachine.turnCrank(); System.out.println(gumballMachine); } }
五、UML 类图 下面是上述例子的 PlantUML 代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @startuml skinparam classAttributeIconSize 0 hide empty members interface State { + void insertCoin() + void ejectCoin() + void turnCrank() + void dispense() } class GumballMachine { - State currentState - int count + void insertCoin() + void ejectCoin() + void turnCrank() + void setState(State s) } class NoCoinState implements State class HasCoinState implements State class SoldState implements State class SoldOutState implements State ' Context has a State GumballMachine o-- "1" State ' ConcreteStates may change the Context's state NoCoinState ..> GumballMachine HasCoinState ..> GumballMachine SoldState ..> GumballMachine SoldOutState ..> GumballMachine @enduml
六、优缺点分析 优点 (Pros):
将状态相关的代码局部化 :将与特定状态相关的代码都放在一个类中,使得代码更加内聚,易于理解和维护。
轻松增加新状态 :增加一个新的状态只需添加一个新的具体状态类,符合开闭原则。
消除庞大的条件分支 :用多态性代替了 if-else 或 switch,使得代码更简洁,更面向对象。
状态转换更明确 :状态的转换逻辑在具体状态类中清晰地表达出来,而不是散落在各处。
缺点 (Cons):
类数量增多 :状态模式会导致系统中类的数量增加,如果状态很简单或者很少,可能会有点“小题大做”。
七、在 Java 中的应用 状态模式在需要对对象生命周期进行精细管理的场景中非常有用。
**工作流引擎 (Workflow Engine)**:一个工单(Ticket)对象可以在“待处理”、“处理中”、“已解决”、“已关闭”等状态之间流转。每个状态下,允许的操作(如“分配”、“解决”)都不同。
网络连接管理 :一个 Connection 对象可以有“正在连接”、“已连接”、“已断开”、“超时”等状态,每个状态下对 send() 或 receive() 的响应都不同。
游戏开发 :游戏角色的状态机(站立、行走、跑步、跳跃、攻击),使用状态模式可以清晰地管理角色行为和动画的切换。