1. 简介
当对象可能会根据不同的情况做出不同的行为,我们就把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。
当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。
对这种有状态的对象编程有两种解决方法:
传统的解决方案:
使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。
但是显然这种做法存在弊端:
条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。
状态模式的解决方案:
当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理。
这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。
状态模式
允许对象在内部状态改变时改变它的行为,对象看起来好像被修改了它的类
3. 适用场景
通常在以下情况下可以考虑使用状态模式。
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
Unity中的动画控制器本质上来讲就是个使用状态模式的有限状态机
4. 模式结构
状态模式包含以下主要角色。
- 环境类(Context)角色:
也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
- 抽象状态(State)角色:
定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
- 具体状态(Concrete State)角色:
实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
类图如下图所示:
5. 状态机
为了能够更好的描述状态模式中各种状态之间的转换关系,我们一般会使用有限状态机来更加具体的描述它。
5.1 什么是状态机
定义:
有限状态机是一种用来进行对象行为建模的工具,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。
要素:
- 现态:
是指当前所处的状态。
- 条件:
又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
- 动作:
条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
- 次态:
条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
5.2 如何实现状态机
实现状态机主要体现在两部:
画出状态转换图和将有限状态机用代码实现。
其中代码实现部分其实就是状态模式的实现。
画出状态转换图
- 找出所有状态(圆圈表示)
- 找出所有状态间的转换条件(圆圈间的线段表示)
- 分析每个状态需要执行的策略(圆圈边的大括号表示)
将有限状态机用代码实现
- 定义一个状态机类
- 根据状态转换图的状态节点(圆圈)**,定义状态**(可使用类(推荐),枚举或无符号整数)
- 根据状态转换图的转换条件(边)**,实现转换动作**方法
6. 模式案例
6.1 案例分析
案例描述:
用状态模式设计一个多线程的状态转换程序
状态分析:
多线程存在 5 种状态,分别为:
- 新建状态(New)
- 就绪状态(Runnable )
- 运行状态(Running)
- 阻塞状态(Blocked)
- 死亡状态(Dead)
各个状态当遇到相关方法调用或事件触发时会转换到其他状态。
状态转换图:
6.2 类定义
6.3 代码实现
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
| public class ScoreStateTest { public static void main(String[] args) { ThreadContext context = new ThreadContext(); context.start(); context.getCPU(); context.suspend(); context.resume(); context.getCPU(); context.stop(); } }
class ThreadContext { private ThreadState state; ThreadContext() { state = new New(); } public void setState(ThreadState state) { this.state = state; } public ThreadState getState() { return state; } public void start() { ((New) state).start(this); } public void getCPU() { ((Runnable) state).getCPU(this); } public void suspend() { ((Running) state).suspend(this); } public void stop() { ((Running) state).stop(this); } public void resume() { ((Blocked) state).resume(this); } }
abstract class ThreadState { protected String stateName; }
class New extends ThreadState { public New() { stateName = "新建状态"; System.out.println("当前线程处于:新建状态."); } public void start(ThreadContext hj) { System.out.print("调用start()方法-->"); if (stateName.equals("新建状态")) { hj.setState(new Runnable()); } else { System.out.println("当前线程不是新建状态,不能调用start()方法."); } } }
class Runnable extends ThreadState { public Runnable() { stateName = "就绪状态"; System.out.println("当前线程处于:就绪状态."); } public void getCPU(ThreadContext hj) { System.out.print("获得CPU时间-->"); if (stateName.equals("就绪状态")) { hj.setState(new Running()); } else { System.out.println("当前线程不是就绪状态,不能获取CPU."); } } }
class Running extends ThreadState { public Running() { stateName = "运行状态"; System.out.println("当前线程处于:运行状态."); } public void suspend(ThreadContext hj) { System.out.print("调用suspend()方法-->"); if (stateName.equals("运行状态")) { hj.setState(new Blocked()); } else { System.out.println("当前线程不是运行状态,不能调用suspend()方法."); } } public void stop(ThreadContext hj) { System.out.print("调用stop()方法-->"); if (stateName.equals("运行状态")) { hj.setState(new Dead()); } else { System.out.println("当前线程不是运行状态,不能调用stop()方法."); } } }
class Blocked extends ThreadState { public Blocked() { stateName = "阻塞状态"; System.out.println("当前线程处于:阻塞状态."); } public void resume(ThreadContext hj) { System.out.print("调用resume()方法-->"); if (stateName.equals("阻塞状态")) { hj.setState(new Runnable()); } else { System.out.println("当前线程不是阻塞状态,不能调用resume()方法."); } } }
class Dead extends ThreadState { public Dead() { stateName = "死亡状态"; System.out.println("当前线程处于:死亡状态."); } }
|
程序运行结果如下:
1 2 3 4 5 6 7
| 当前线程处于:新建状态. 调用start()方法 获得CPU时间 调用suspend()方法 调用resume()方法 获得CPU时间 调用stop()方法
|
7. 总结
7.2 优缺点
优点:
- 结构清晰:
状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 将状态转换显示化,减少对象间的相互依赖:
将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 状态类职责明确,有利于程序的扩展:
通过定义新的子类很容易地增加新的状态和转换。
缺点:
- 状态模式的使用必然会增加系统的类与对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
- 状态模式对开闭原则的支持并不太好:
对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。
7.3 与其他模式的区别
状态模式与策略模式看起来像双胞胎,但他们还是不相同的。
模式 |
描述 |
策略模式 |
将可以互换的行为封装,使用委托的方法决定使用哪个行为 |
状态莫斯 |
封装基于状态的行为,将行为委托至当前状态 |
模板方法 |
由子类决定如何实现算法中的某些步骤 |