状态模式并不常使用,但是在能够使用上的场景中,能够发挥很大的作用
状态模式一般用于实现状态机,状态机常用于游戏,工作流等引擎的开发,不过,状态机的实现方式很多
其支持状态模式和分支逻辑法和查表法,我们可以利用其比较下优劣
什么是有限状态机,状态state,事件event,动作Action,其中事件被称为转移条件,事件触发状态的转移和动作的执行,不过动作是个选填,也可以只转移状态而不执行动作
一个案例可以说,超级马里奥,在其中,马里奥具有多种状态 小马里奥,大马里奥,火焰马里奥,斗篷马里奥,彼此之间可以转换,而且会获取不同的积分,比如,初始的形态是小马里奥,吃了蘑菇就是超级马里奥,并且增加了100积分
实际上,形态的转变就是一个状态机,不同的形态就是不同的状态,事件就是吃蘑菇,动作就是变大小并且加积分
吃蘑菇的整体流程就是,从小马里奥变为大马里奥(状态改变),并且加了积分(执行动作)
那么,整体的改变就是下面
上面就是简单的自动状态机的改变
关于上面的代码的实现,可以如下所示,其中obtainMushRoom(),obtainCape(),obtainFireFlower(),meetMonster()都是触发的事件,可以改变当前的状态,并且执行对应的增减积分动作
public enum State {
SMALL(0), SUPER(1), FIRE(2), CAPE(3); private int value; private State(int value) { this.value = value; } public int getValue() { return this.value; } } public class MarioStateMachine { private int score; private State currentState; public MarioStateMachine() { this.score = 0; this.currentState = State.SMALL; } public void obtainMushRoom() { //TODO } public void obtainCape() { //TODO } public void obtainFireFlower() { //TODO } public void meetMonster() { //TODO } public int getScore() { return this.score; } public State getCurrentState() { return this.currentState; } } public class ApplicationDemo { public static void main(String[] args) { MarioStateMachine mario = new MarioStateMachine(); mario.obtainMushRoom(); int score = mario.getScore(); State state = mario.getCurrentState(); System.out.println(“mario score: ” + score + “; state: ” + state); } } |
上面就是简单的函数和状态的改变,接下来我们会说一下如何实现状态机
对于如何实现状态机,我们常见的有三种实现方式
1,简单的实现,就是直译为代码,其中会包含大量的if-else switch-case的判断,这种方法被称为分支逻辑法
下面就是代码的直接表现
public class MarioStateMachine {
private int score; private State currentState; public MarioStateMachine() { this.score = 0; this.currentState = State.SMALL; } public void obtainMushRoom() { if (currentState.equals(State.SMALL)) { this.currentState = State.SUPER; this.score += 100; } } public void obtainCape() { if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER) ) { this.currentState = State.CAPE; this.score += 200; } } public void obtainFireFlower() { if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER) ) { this.currentState = State.FIRE; this.score += 300; } } public void meetMonster() { if (currentState.equals(State.SUPER)) { this.currentState = State.SMALL; this.score -= 100; return; } if (currentState.equals(State.CAPE)) { this.currentState = State.SMALL; this.score -= 200; return; } if (currentState.equals(State.FIRE)) { this.currentState = State.SMALL; this.score -= 300; return; } } public int getScore() { return this.score; } public State getCurrentState() { return this.currentState; } } |
对于简单的状态机来说,这是可以接受的,但是对于复杂的状态机来说,实现起来让其出现遗漏,比如状态转移的转换忘写了,而且代码中充斥着大量的if-else,或者 switch-case分支判断逻辑,可读性和可维护性差,如果哪天改了某个状态,就要找到对应的代码进行修改
二,查表法
利用一个二维表来进行表示状态机,第一维表示状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态以及执行的动作
相对于分支逻辑的实现方式,查表法的实现更加稳定,清洗,可修改
修改状态的时候,只需要修改transition Table和actionTable两个二维数组即可,实际上,可以把这两个二维数组存在配置文件,修改状态机的时候,直接修改配置文件即可,具体的代码实现逻辑如下,利用了两个二维数组,从枚举中获取了值然后从二维数组中取值并且更新数据
public enum Event {
GOT_MUSHROOM(0), GOT_CAPE(1), GOT_FIRE(2), MET_MONSTER(3); private int value; private Event(int value) { this.value = value; } public int getValue() { return this.value; } } public class MarioStateMachine { private int score; private State currentState; private static final State[][] transitionTable = { {SUPER, CAPE, FIRE, SMALL}, {SUPER, CAPE, FIRE, SMALL}, {CAPE, CAPE, CAPE, SMALL}, {FIRE, FIRE, FIRE, SMALL} }; private static final int[][] actionTable = { {+100, +200, +300, +0}, {+0, +200, +300, -100}, {+0, +0, +0, -200}, {+0, +0, +0, -300} }; public MarioStateMachine() { this.score = 0; this.currentState = State.SMALL; } public void obtainMushRoom() { executeEvent(Event.GOT_MUSHROOM); } public void obtainCape() { executeEvent(Event.GOT_CAPE); } public void obtainFireFlower() { executeEvent(Event.GOT_FIRE); } public void meetMonster() { executeEvent(Event.MET_MONSTER); } private void executeEvent(Event event) { int stateValue = currentState.getValue(); int eventValue = event.getValue(); this.currentState = transitionTable[stateValue][eventValue]; this.score = actionTable[stateValue][eventValue]; } public int getScore() { return this.score; } public State getCurrentState() { return this.currentState; } } |
实现的方法三:状态模式
事件的触发的动作只是积分的加减,所以,看起来一个int类型的二维数组就可以完成,二维数组中的值表示积分的加减,但是,如果执行的动作并非那么简单,而是一系列复杂的逻辑操作,比如写库,发送通知等,那么就需要在查表法的基础上进行很多的维护了,这时候,就可以使用第三种方法实现了,状态模式,利用多个类进行维护触发的事件,和执行逻辑,我们进行补全了下面的代码,补全后的代码如下,其中IMario是状态的接口,定义了所有的事件,SmallMario,SuperMario,CapeMario,FireMario都是对应的实现类,分别对应了状态机的状态,原来的状态转移和动作执行的代码逻辑,都在MarioStateMachine类中,这些代码逻辑分散到各个状态类中
public interface IMario { //所有状态类的接口
State getName(); //以下是定义的事件 void obtainMushRoom(); void obtainCape(); void obtainFireFlower(); void meetMonster(); } public class SmallMario implements IMario { private MarioStateMachine stateMachine; public SmallMario(MarioStateMachine stateMachine) { this.stateMachine = stateMachine; } @Override public State getName() { return State.SMALL; } @Override public void obtainMushRoom() { stateMachine.setCurrentState(new SuperMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() + 100); } @Override public void obtainCape() { stateMachine.setCurrentState(new CapeMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() + 200); } @Override public void obtainFireFlower() { stateMachine.setCurrentState(new FireMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() + 300); } @Override public void meetMonster() { // do nothing… } } public class SuperMario implements IMario { private MarioStateMachine stateMachine; public SuperMario(MarioStateMachine stateMachine) { this.stateMachine = stateMachine; } @Override public State getName() { return State.SUPER; } @Override public void obtainMushRoom() { // do nothing… } @Override public void obtainCape() { stateMachine.setCurrentState(new CapeMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() + 200); } @Override public void obtainFireFlower() { stateMachine.setCurrentState(new FireMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() + 300); } @Override public void meetMonster() { stateMachine.setCurrentState(new SmallMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() – 100); } } // 省略CapeMario、FireMario类… public class MarioStateMachine { private int score; private IMario currentState; // 不再使用枚举来表示状态 public MarioStateMachine() { this.score = 0; this.currentState = new SmallMario(this); } public void obtainMushRoom() { this.currentState.obtainMushRoom(); } public void obtainCape() { this.currentState.obtainCape(); } public void obtainFireFlower() { this.currentState.obtainFireFlower(); } public void meetMonster() { this.currentState.meetMonster(); } public int getScore() { return this.score; } public State getCurrentState() { return this.currentState.getName(); } public void setScore(int score) { this.score = score; } public void setCurrentState(IMario currentState) { this.currentState = currentState; } } |
对于状态比较多的业务来说,使用查表发会比较好一点,而处理逻辑较为复杂的业务来说,状态模式更加不错,需要针对性的不同使用
本章重点
对于状态模式,其实就是状态机的一种实现
状态机由三个部分组成 状态 事件 动作,其中事件又被称为转移条件,触发后执行动作修改状态
而且对应的实现方式主要有三种
1.直接编译,又名分支逻辑法,使用if-else或者switch-case来进行状态转移,进行点的直译
2.查表法,对于多种状态的选择比较合适
3.状态模式,对于状态不多,但是动作和处理业务复杂的状态机来说,很合适