1:状态模式
状态模式是一种行为设计模式,让你能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。
2:状态模式问题
状态模式与有限状态机的概念紧密相关。
其主要思想是程序在任意时刻仅可处于几种有限的状态中。在任何一个特定状态中,程序的行为都不相同,且可瞬间从一个状态切换到另一个状态。不过,根据当前状态,程序可能会切换到另外一种状态,也可能会保持当前状态不变。这些数量有限且预先定义的状态切换规则被称为转移。
对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。
3:状态模式解决方案
状态模式建议为对象的所有可能状态新建一个类,然后将所有状态的对应行为抽取到这些类中。
原始对象被称为上下文(context),它并不会自行实现所有行为,而是会保存一个指向表示当前状态的状态对象的引用,且将所有与状态相关的工作委派给该对象。
如需将上下文转换为另外一种状态,则需将当前活动的状态对象替换为另外一个代表新状态的对象。采用这种方式是有前提的:所有状态类都必须遵循同样的接口,而且上下文必须仅通过接口与这些对象进行交互。
状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。
4:状态模式结构
- 上下文(Context)保存了对于一个具体状态对象的引用,并会将所有与该状态相关的工作委派给它。上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。
- 状态(State)接口会声明特定于状态的方法。这些方法应能被其他所有具体状态所理解,因为你不希望某些状态所拥有的方法永远不会被调用。
- 具体状态(Concrete States)会自行实现特定于状态的方法。为了避免多个状态中包含相似代码,你可以提供一个封装有部分通用行为的中间抽象类。
状态对象可存储对于上下文对象的反向引用。状态可以通过该引用从上下文处获取所需信息,并且能触发状态转移。
- 上下文和具体状态都可以设置上下文的下个状态,并可通过替换连接到上下文的状态对象来完成实际的状态转换。
代码示例:
本实例包含了“不及格”“中等”和“优秀” 3 种状态,当学生的分数小于 60 分时为“不及格”状态,当分数大于等于 60 分且小于 90 分时为“中等”状态,当分数大于等于 90 分时为“优秀”状态,我们用状态模式来实现这个程序。
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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
|
class ScoreContext{ private AbstractState state;
ScoreContext(){ state = new LowState(this); }
public AbstractState getState() { return state; }
public void setState(AbstractState state) { this.state = state; }
public void add(int score){ state.addScore(score); } }
abstract class AbstractState{
protected ScoreContext scoreContext;
protected String stateName;
protected int score;
protected static int LOW = 60;
protected static int HIGH = 90;
public abstract void checkState();
public void addScore(int x){ score += x; System.out.print("加上:" + x + "分,\t当前分数:" + score); checkState(); System.out.println("分,\t当前状态:" + scoreContext.getState().stateName); } }
class LowState extends AbstractState{
public LowState(ScoreContext context){ scoreContext = context; stateName="不及格"; score = 0; }
public LowState(AbstractState state){ scoreContext=state.scoreContext; stateName="不及格"; score = state.score; }
@Override public void checkState() { if (score >= HIGH) { scoreContext.setState(new HighState(this)); } else if (score >= LOW) { scoreContext.setState(new MiddleState(this)); } } }
class MiddleState extends AbstractState{
public MiddleState(AbstractState state){ scoreContext=state.scoreContext; stateName="中等"; score = state.score; }
@Override public void checkState() { if (score >= HIGH) { scoreContext.setState(new HighState(this)); } else if (score < LOW) { scoreContext.setState(new LowState(this)); } } }
class HighState extends AbstractState{
public HighState(AbstractState state) { scoreContext = state.scoreContext; stateName = "优秀"; score = state.score; }
@Override public void checkState() { if (score < LOW) { scoreContext.setState(new LowState(this)); } else if (score < HIGH) { scoreContext.setState(new MiddleState(this)); } } }
public class StateDemo { public static void main(String[] args) { ScoreContext scoreContext = new ScoreContext(); scoreContext.add(30); scoreContext.add(30); scoreContext.add(30); scoreContext.add(5); scoreContext.add(-10); scoreContext.add(-30); } }
|
5:状态模式适用场景
- 如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可使用状态模式。
- 如果某个类需要根据成员变量的当前值改变自身行为,从而需要使用大量的条件语句时,可使用该模式。
- 当相似状态和基于条件的状态机转换中存在许多重复代码时,可使用状态模式。
6:状态模式优缺点
优点 |
缺点 |
单一职责原则。 将与特定状态相关的代码放在单独的类中。 |
如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作。 |
开闭原则。 无需修改已有状态类和上下文就能引入新状态。 |
|
通过消除臃肿的状态机条件语句简化上下文代码。 |
|
7:状态模式与其他模式关系
- 桥接模式、状态模式和策略模式(在某种程度上包括适配器模式)模式的接口非常相似。实际上,它们都基于组合模式——即将工作委派给其他对象,不过也各自解决了不同的问题。模式并不只是以特定方式组织代码的配方,你还可以使用它们来和其他开发者讨论模式所解决的问题。
- 状态可被视为策略的扩展。两者都基于组合机制:它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。策略使得这些对象相互之间完全独立,它们不知道其他对象的存在。但状态模式没有限制具体状态之间的依赖,且允许它们自行改变在不同情景下的状态。
8:状态模式举例
拿手机的自动切换屏幕方向举例:默认你是竖屏方向,重力感应到切换横屏,系统会自行切换状态。在不同状态点击不同按钮也交给这个状态自行判断。
9:参考