状态模式并不常使用,但是在能够使用上的场景中,能够发挥很大的作用

状态模式一般用于实现状态机,状态机常用于游戏,工作流等引擎的开发,不过,状态机的实现方式很多

其支持状态模式和分支逻辑法和查表法,我们可以利用其比较下优劣

什么是有限状态机,状态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.状态模式,对于状态不多,但是动作和处理业务复杂的状态机来说,很合适

发表评论

邮箱地址不会被公开。 必填项已用*标注