7.4.3 状态委托
接下来,我们在主角类中定义一个指针变量,让它指向当前的状态。我们把之前那个很大的switch去掉,并让它去调用状态接口的虚函数,最终这些虚方法就会动态的调用具体子状态的相应函数。
状态委托看起来很像策略模式和类型对象模式(第13章)。在这三个模式中,你会有一个主对象委托给另外的附属对象。
它们的区别主要在于目的不同:
- 策略模式的目标是将主类与它的部分行为进行解耦
- 类型对象模式目的是使得多个对象通过共享相同类型对象的引用来表现出相似性
- 状态模式的目标是通过改变主对象代理的对象来改变主对象的行为
- 有限状态机:
同时只能进入一种状态,每种状态有自己的进入条件,以及转出条件;
在自己的状态里,做本状态要做的事,并判断是否满足转出条件。
但是它有一定的局限性,比如弄更复杂点的东西。
- 并发状态机:
我们决定给我们的主角添加持枪功能。当她持枪的时候,她仍然可以:跑、跳、躲避等。但是,她也需要能够在这些状态过程中开火。
如果你执着于传统的有限状态机,那我们可能需要把之前的状态加倍。对于每一个已经存在的状态,我们需要定义另一个状态,它做的事情也差不多,不过就是多了持枪的操作。比如站立状态和站立开火状态,跳跃状态和跳跃开火状态等。
如果我们添加更多的武器种类,那么这个状态数量将会急剧增加。而且不仅仅是增加了大量的状态类实例,它还会增加大量的冗余,实际上带不带枪的状态仅有是否包含开火代码的区别而已。
这里的问题是,我们把两种状态杂合在一起了。我们把两种不同的状态硬塞到一个状态机里面去了。以所有可能出现的组合建模,我们可能需要为每一种状态准备一组状态。解决方法比较直观,就是分开成两个状态机。
在实际中,你可能会发现你需要对某些状态处理进行干预。比如,如果主角不能够在条约的过程中开火,或者她在装备武器的时候不能俯冲,为了处理这种情况,在代码里,对于每一个状态,你可能需要做一些简单的if判断并作出特殊处理。虽然这可能不是最好的解决方案(增加特殊情况),但是至少它可以完成任务。
功能更加完备的系统可能会让一个状态机来处理输入,以便另外一个状态机不会接收到输入。这样将能防止两个状态机对同一输入进行错误的响应。
- 层次状态机:
在我们把主角的行为更加具象化以后,她可能会包含大量相似的状态。比如,她可能有站立、走路、跑步、滑动等,在这些状态中的任何一个状态时按下B键,我们的主角就要跳跃;按下方向键,我们的主角就要躲避。
如果只是使用一个简单的状态机实现,我们可能会在这些状态中重复不少代码。更好的解决方案是,我们只需要实现一次,然后它便可以在所有的状态下都复用。
一种共享代码的方式是继承:当一个事件进来的时候,如果子状态不处理它,那么沿着继承链,传给它的父状态来处理。换句话说,它有点像覆盖继承的方法。
你可以在基类中,使用状态栈,而不是单单一个状态,来更加明确的表示父状态的状态链。
我们当前的状态总是处于栈顶,栈顶下面的第一个元素是它的父状态,再下一个状态是它的父状态的父状态,以此类推。如果你要进行一些与状态相关的行为操作,那么首先从栈顶状态开始,如果它不处理,则往下寻找直到找到一个能处理此事件的状态为止(如果找遍整个栈,还没能被处理掉,则将此事件被忽略掉)。
-
下推自动机
还有一种有限状态机的扩展,它们也是用状态栈。容易让人混淆的是,这里的栈代表了完全不同的东西,且用于解决一个完全不同的问题。
它要解决的是有限状态机没有历史记录的问题。我们知道当前状态,但是,我们并不知道之前的状态是什么。而且,我们也没有简便的方法可以获取之前的状态。