64 | 状态模式:游戏、工作流引擎中常用的状态机是如何实现的?

王争 2020-03-30

64 - 图1

00:00

1.0x

讲述:冯永吉大小:9.36M时长:10:13

从今天起,我们开始学习状态模式。在实际的软件开发中,状态模式并不是很常用,但是在能够用到的场景里,它可以发挥很大的作用。从这一点上来看,它有点像我们之前讲到的组合模式。

状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。不过,状态机的实现方式有多种,除了状态模式,比较常用的还有分支逻辑法和查表法。今天,我们就详细讲讲这几种实现方式,并且对比一下它们的优劣和应用场景。

话不多说,让我们正式开始今天的学习吧!

什么是有限状态机?

有限状态机,英文翻译是 Finite State Machine,缩写为 FSM,简称为状态机。状态机有 3 个组成部分:状态(State)、事件(Event)、动作(Action)。其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。

对于刚刚给出的状态机的定义,我结合一个具体的例子,来进一步解释一下。

“超级马里奥”游戏不知道你玩过没有?在游戏中,马里奥可以变身为多种形态,比如小马里奥(Small Mario)、超级马里奥(Super Mario)、火焰马里奥(Fire Mario)、斗篷马里奥(Cape Mario)等等。在不同的游戏情节下,各个形态会互相转化,并相应的增减积分。比如,初始形态是小马里奥,吃了蘑菇之后就会变成超级马里奥,并且增加 100 积分。

实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)。

为了方便接下来的讲解,我对游戏背景做了简化,只保留了部分状态和事件。简化之后的状态转移如下图所示:

64 - 图2

我们如何编程来实现上面的状态机呢?换句话说,如何将上面的状态转移图翻译成代码呢?

我写了一个骨架代码,如下所示。其中,obtainMushRoom()、obtainCape()、obtainFireFlower()、meetMonster() 这几个函数,能够根据当前的状态和事件,更新状态和增减积分。不过,具体的代码实现我暂时并没有给出。你可以把它当做面试题,试着补全一下,然后再来看我下面的讲解,这样你的收获会更大。

public enum State {

SMALL(0),

SUPER(1),

FIRE(2),

CAPE(3);

private int value;

private State(int value) {

  1. this.value = value;

}

public int getValue() {

  1. return this.value;

}

}

public class MarioStateMachine {

private int score;

private State currentState;

public MarioStateMachine() {

  1. this.score = 0;
  2. this.currentState = State.SMALL;

}

public void obtainMushRoom() {

  1. //TODO

}

public void obtainCape() {

  1. //TODO

}

public void obtainFireFlower() {

  1. //TODO

}

public void meetMonster() {

  1. //TODO

}

public int getScore() {

  1. return this.score;

}

public State getCurrentState() {

  1. return this.currentState;

}

}

public class ApplicationDemo {

public static void main(String[] args) {

  1. MarioStateMachine mario = new MarioStateMachine();
  2. mario.obtainMushRoom();
  3. int score = mario.getScore();
  4. State state = mario.getCurrentState();
  5. System.out.println("mario score: " + score + "; state: " + state);

}

}

状态机实现方式一:分支逻辑法

对于如何实现状态机,我总结了三种方式。其中,最简单直接的实现方式是,参照状态转移图,将每一个状态转移,原模原样地直译成代码。这样编写的代码会包含大量的 if-else 或 switch-case 分支判断逻辑,甚至是嵌套的分支判断逻辑,所以,我把这种方法暂且命名为分支逻辑法。

按照这个实现思路,我将上面的骨架代码补全一下。补全之后的代码如下所示:

public class MarioStateMachine {

private int score;

private State currentState;

public MarioStateMachine() {

  1. this.score = 0;
  2. this.currentState = State.SMALL;

}

public void obtainMushRoom() {

  1. if (currentState.equals(State.SMALL)) {
  2. this.currentState = State.SUPER;
  3. this.score += 100;
  4. }

}

public void obtainCape() {

  1. if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER) ) {
  2. this.currentState = State.CAPE;
  3. this.score += 200;
  4. }

}

public void obtainFireFlower() {

  1. if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER) ) {
  2. this.currentState = State.FIRE;
  3. this.score += 300;
  4. }

}

public void meetMonster() {

  1. if (currentState.equals(State.SUPER)) {
  2. this.currentState = State.SMALL;
  3. this.score -= 100;
  4. return;
  5. }
  6. if (currentState.equals(State.CAPE)) {
  7. this.currentState = State.SMALL;
  8. this.score -= 200;
  9. return;
  10. }
  11. if (currentState.equals(State.FIRE)) {
  12. this.currentState = State.SMALL;
  13. this.score -= 300;
  14. return;
  15. }

}

public int getScore() {

  1. return this.score;

}

public State getCurrentState() {

  1. return this.currentState;

}

}

对于简单的状态机来说,分支逻辑这种实现方式是可以接受的。但是,对于复杂的状态机来说,这种实现方式极易漏写或者错写某个状态转移。除此之外,代码中充斥着大量的 if-else 或者 switch-case 分支判断逻辑,可读性和可维护性都很差。如果哪天修改了状态机中的某个状态转移,我们要在冗长的分支逻辑中找到对应的代码进行修改,很容易改错,引入 bug。

状态机实现方式二:查表法

实际上,上面这种实现方法有点类似 hard code,对于复杂的状态机来说不适用,而状态机的第二种实现方式查表法,就更加合适了。接下来,我们就一块儿来看下,如何利用查表法来补全骨架代码。

实际上,除了用状态转移图来表示之外,状态机还可以用二维表来表示,如下所示。在这个二维表中,第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。

64 - 图3

相对于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改 transitionTable 和 actionTable 两个二维数组即可。实际上,如果我们把这两个二维数组存储在配置文件中,当需要修改状态机时,我们甚至可以不修改任何代码,只需要修改配置文件就可以了。具体的代码如下所示:

public enum Event {

GOT_MUSHROOM(0),

GOT_CAPE(1),

GOT_FIRE(2),

MET_MONSTER(3);

private int value;

private Event(int value) {

  1. this.value = value;

}

public int getValue() {

  1. return this.value;

}

}

public class MarioStateMachine {

private int score;

private State currentState;

private static final State[][] transitionTable = {

  1. {SUPER, CAPE, FIRE, SMALL},
  2. {SUPER, CAPE, FIRE, SMALL},
  3. {CAPE, CAPE, CAPE, SMALL},
  4. {FIRE, FIRE, FIRE, SMALL}

};

private static final int[][] actionTable = {

  1. {+100, +200, +300, +0},
  2. {+0, +200, +300, -100},
  3. {+0, +0, +0, -200},
  4. {+0, +0, +0, -300}

};

public MarioStateMachine() {

  1. this.score = 0;
  2. this.currentState = State.SMALL;

}

public void obtainMushRoom() {

  1. executeEvent(Event.GOT_MUSHROOM);

}

public void obtainCape() {

  1. executeEvent(Event.GOT_CAPE);

}

public void obtainFireFlower() {

  1. executeEvent(Event.GOT_FIRE);

}

public void meetMonster() {

  1. executeEvent(Event.MET_MONSTER);

}

private void executeEvent(Event event) {

  1. int stateValue = currentState.getValue();
  2. int eventValue = event.getValue();
  3. this.currentState = transitionTable[stateValue][eventValue];
  4. this.score += actionTable[stateValue][eventValue];

}

public int getScore() {

  1. return this.score;

}

public State getCurrentState() {

  1. return this.currentState;

}

}

状态机实现方式三:状态模式

在查表法的代码实现中,事件触发的动作只是简单的积分加减,所以,我们用一个 int 类型的二维数组 actionTable 就能表示,二维数组中的值表示积分的加减值。但是,如果要执行的动作并非这么简单,而是一系列复杂的逻辑操作(比如加减积分、写数据库,还有可能发送消息通知等等),我们就没法用如此简单的二维数组来表示了。这也就是说,查表法的实现方式有一定局限性。

虽然分支逻辑的实现方式不存在这个问题,但它又存在前面讲到的其他问题,比如分支判断逻辑较多,导致代码可读性和可维护性不好等。实际上,针对分支逻辑法存在的问题,我们可以使用状态模式来解决。

状态模式通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑。我们还是结合代码来理解这句话。

利用状态模式,我们来补全 MarioStateMachine 类,补全后的代码如下所示。

其中,IMario 是状态的接口,定义了所有的事件。SmallMario、SuperMario、CapeMario、FireMario 是 IMario 接口的实现类,分别对应状态机中的 4 个状态。原来所有的状态转移和动作执行的代码逻辑,都集中在 MarioStateMachine 类中,现在,这些代码逻辑被分散到了这 4 个状态类中。

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) {

  1. this.stateMachine = stateMachine;

}

@Override

public State getName() {

  1. return State.SMALL;

}

@Override

public void obtainMushRoom() {

  1. stateMachine.setCurrentState(new SuperMario(stateMachine));
  2. stateMachine.setScore(stateMachine.getScore() + 100);

}

@Override

public void obtainCape() {

  1. stateMachine.setCurrentState(new CapeMario(stateMachine));
  2. stateMachine.setScore(stateMachine.getScore() + 200);

}

@Override

public void obtainFireFlower() {

  1. stateMachine.setCurrentState(new FireMario(stateMachine));
  2. stateMachine.setScore(stateMachine.getScore() + 300);

}

@Override

public void meetMonster() {

  1. // do nothing...

}

}

public class SuperMario implements IMario {

private MarioStateMachine stateMachine;

public SuperMario(MarioStateMachine stateMachine) {

  1. this.stateMachine = stateMachine;

}

@Override

public State getName() {

  1. return State.SUPER;

}

@Override

public void obtainMushRoom() {

  1. // do nothing...

}

@Override

public void obtainCape() {

  1. stateMachine.setCurrentState(new CapeMario(stateMachine));
  2. stateMachine.setScore(stateMachine.getScore() + 200);

}

@Override

public void obtainFireFlower() {

  1. stateMachine.setCurrentState(new FireMario(stateMachine));
  2. stateMachine.setScore(stateMachine.getScore() + 300);

}

@Override

public void meetMonster() {

  1. stateMachine.setCurrentState(new SmallMario(stateMachine));
  2. stateMachine.setScore(stateMachine.getScore() - 100);

}

}

// 省略CapeMario、FireMario类…

public class MarioStateMachine {

private int score;

private IMario currentState; // 不再使用枚举来表示状态

public MarioStateMachine() {

  1. this.score = 0;
  2. this.currentState = new SmallMario(this);

}

public void obtainMushRoom() {

  1. this.currentState.obtainMushRoom();

}

public void obtainCape() {

  1. this.currentState.obtainCape();

}

public void obtainFireFlower() {

  1. this.currentState.obtainFireFlower();

}

public void meetMonster() {

  1. this.currentState.meetMonster();

}

public int getScore() {

  1. return this.score;

}

public State getCurrentState() {

  1. return this.currentState.getName();

}

public void setScore(int score) {

  1. this.score = score;

}

public void setCurrentState(IMario currentState) {

  1. this.currentState = currentState;

}

}

上面的代码实现不难看懂,我只强调其中的一点,即 MarioStateMachine 和各个状态类之间是双向依赖关系。MarioStateMachine 依赖各个状态类是理所当然的,但是,反过来,各个状态类为什么要依赖 MarioStateMachine 呢?这是因为,各个状态类需要更新 MarioStateMachine 中的两个变量,score 和 currentState。

实际上,上面的代码还可以继续优化,我们可以将状态类设计成单例,毕竟状态类中不包含任何成员变量。但是,当将状态类设计成单例之后,我们就无法通过构造函数来传递 MarioStateMachine 了,而状态类又要依赖 MarioStateMachine,那该如何解决这个问题呢?

实际上,在第 42 讲单例模式的讲解中,我们提到过几种解决方法,你可以回过头去再查看一下。在这里,我们可以通过函数参数将 MarioStateMachine 传递进状态类。根据这个设计思路,我们对上面的代码进行重构。重构之后的代码如下所示:

public interface IMario {

State getName();

void obtainMushRoom(MarioStateMachine stateMachine);

void obtainCape(MarioStateMachine stateMachine);

void obtainFireFlower(MarioStateMachine stateMachine);

void meetMonster(MarioStateMachine stateMachine);

}

public class SmallMario implements IMario {

private static final SmallMario instance = new SmallMario();

private SmallMario() {}

public static SmallMario getInstance() {

  1. return instance;

}

@Override

public State getName() {

  1. return State.SMALL;

}

@Override

public void obtainMushRoom(MarioStateMachine stateMachine) {

  1. stateMachine.setCurrentState(SuperMario.getInstance());
  2. stateMachine.setScore(stateMachine.getScore() + 100);

}

@Override

public void obtainCape(MarioStateMachine stateMachine) {

  1. stateMachine.setCurrentState(CapeMario.getInstance());
  2. stateMachine.setScore(stateMachine.getScore() + 200);

}

@Override

public void obtainFireFlower(MarioStateMachine stateMachine) {

  1. stateMachine.setCurrentState(FireMario.getInstance());
  2. stateMachine.setScore(stateMachine.getScore() + 300);

}

@Override

public void meetMonster(MarioStateMachine stateMachine) {

  1. // do nothing...

}

}

// 省略SuperMario、CapeMario、FireMario类…

public class MarioStateMachine {

private int score;

private IMario currentState;

public MarioStateMachine() {

  1. this.score = 0;
  2. this.currentState = SmallMario.getInstance();

}

public void obtainMushRoom() {

  1. this.currentState.obtainMushRoom(this);

}

public void obtainCape() {

  1. this.currentState.obtainCape(this);

}

public void obtainFireFlower() {

  1. this.currentState.obtainFireFlower(this);

}

public void meetMonster() {

  1. this.currentState.meetMonster(this);

}

public int getScore() {

  1. return this.score;

}

public State getCurrentState() {

  1. return this.currentState.getName();

}

public void setScore(int score) {

  1. this.score = score;

}

public void setCurrentState(IMario currentState) {

  1. this.currentState = currentState;

}

}

实际上,像游戏这种比较复杂的状态机,包含的状态比较多,我优先推荐使用查表法,而状态模式会引入非常多的状态类,会导致代码比较难维护。相反,像电商下单、外卖下单这种类型的状态机,它们的状态并不多,状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能会比较复杂,所以,更加推荐使用状态模式来实现。

重点回顾

好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。

今天我们讲解了状态模式。虽然网上有各种状态模式的定义,但是你只要记住状态模式是状态机的一种实现方式即可。状态机又叫有限状态机,它有 3 个部分组成:状态、事件、动作。其中,事件也称为转移条件。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。

针对状态机,今天我们总结了三种实现方式。

第一种实现方式叫分支逻辑法。利用 if-else 或者 switch-case 分支逻辑,参照状态转移图,将每一个状态转移原模原样地直译成代码。对于简单的状态机来说,这种实现方式最简单、最直接,是首选。

第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。

第三种实现方式叫状态模式。对于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种实现方式。

课堂讨论

状态模式的代码实现还存在一些问题,比如,状态接口中定义了所有的事件函数,这就导致,即便某个状态类并不需要支持其中的某个或者某些事件,但也要实现所有的事件函数。不仅如此,添加一个事件到状态接口,所有的状态类都要做相应的修改。针对这些问题,你有什么解决方法吗?

欢迎留言和我分享你的想法。如果有收获,欢迎你把这篇文章分享给你的朋友。

31人觉得很赞给文章提建议;)

64 - 图4

© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。

64 - 图5

张创琦

Ctrl + Enter 发表

0/2000字

提交留言

精选留言(77)

  • 64 - 图6
    张先生丶
    关于课堂讨论,可以在接口和实现类中间加一层抽象类解决此问题,抽象类实现状态接口,状态类继承抽象类,只需要重写需要的方法即可
    2020-03-30
    _3
    _112

  • 64 - 图7
    Darren64 - 图8
    Java中Spring也有有限状态机的框架 :https://github.com/spring-projects/spring-statemachine
    2020-04-14

    __36

  • 64 - 图9
    下雨天
    课后题
    最小接口原则
    具体做法:状态类只关心与自己相关的接口,将状态接口中定义的事件函数按事件分类,拆分到不同接口中,通过这些新接口的组合重新实现状态类即可!
    2020-03-30
    _1
    _25

  • 64 - 图10
    小晏子
    课后思考:要解决这个问题可以有两种方式1. 直接使用抽象类替代接口,抽象类中对每个时间有个默认的实现,比如抛出unimplemented exception,这样子类要使用的话必须自己实现。2. 就是还是使用接口定义事件,但是额外创建一个抽象类实现这个接口,然后具体的状态实现类继承这个抽象类,这种方式好处在于可扩展性强,可以处理将来有不相关的事件策略加入进来的情况。
    2020-03-30

    __18

  • 64 - 图11
    J.D.Chi
    Flutter里引入了Bloc框架后,就是非常典型的状态模式(或是有限状态机)。https://bloclibrary.dev/#/coreconcepts
    2020-03-30

    __12

  • 64 - 图12
    李小四
    设计模式_63:

    作业

    组合优于继承

  • 即使不需要,也必须实现所有的函数

    最小接口原则,每个函数拆分到单独的接口中
    - 新增事件要修改所有状态实现
    观察者模式,用注解动态地把事件函数注册到观察队列中。
    # 感想
    看到状态接口类中直接使用了obtainMushRoom()这样具体的事件函数,感觉很不舒服。就像结尾的讨论,所有的状态类必须实现所有事件函数,新增一种事件状态接口和实现都要改。。。
    2020-03-30

    __12

  • 64 - 图13
    Jxin
    1.解决方法的话,java可以用接口的def函数解决,也可以在实现类和接口间加一个def实现来过度。但这都是不好的设计。事实上接口def函数的实现是一种无奈之举,我们在使用接口时应依旧遵循其语意限制?而非滥用语言特性。
    2.所以上诉解决方案,个人认为最好的方式就是细分接口包含的函数,对现有的函数重新归类,划分成不同的接口。实现时以实现多接口的方式去组合出目标实现。这也是接口隔离的体现。
    2020-03-30

    __10

  • 64 - 图14
    DFighting
    不太赞同老师的这种抽象方式,状态机中的每个状态应该具有事件、触发条件、转移(方法)列表,每一个应该都可以抽象为一个接口或者泛型类,状态机作为一个单例这个没问题,但是状态机应该只是作为一个状态的注册工厂,里面具有的应该是多种状态,状态间的流转才是状态机最重要的功能抽象。score放在状态和状态机中都不合适,这应该是状态机操纵的一个对象/资源,应该单独抽象出来,在状态间流转
    作者回复: 好吧
    2020-11-21
    _2
    _9

  • 64 - 图15
    进击的前端er
    我有个前端组件,很适合这个模式,我正在尝试重构,发现代码缩减量可能达到50%以上,而且更容易理解
    2020-04-03
    _1
    _9

  • 64 - 图16
    Javatar
    增加一个MarioSupport的抽象类,其中getName是抽象方法。其他四个事件方法都默认实现为空。那么四个具体的状态类,就extend这个MarioSupport,根据自身状态,override需要覆盖的方法就可以了
    2020-07-03

    __4

  • 64 - 图17
    test
    课堂讨论:给新增的方法一个默认实现。
    2020-03-30

    __4

  • 64 - 图18
    Monday
    实际上,上面的代码还可以继续优化,我们可以将状态类设计成单例,毕竟状态类中不包含任何成员变量。但是,当将状态类设计成单例之后,我们就无法通过构造函数来传递 MarioStateMachine 了,而状态类又要依赖 MarioStateMachine,那该如何解决这个问题呢?
    实际上状态类可以设计为单例,MarioStateMachine也可以通过函数的参数方式传入,但是这样做的优势是什么呢?
    2020-04-01
    _1
    _3

  • 64 - 图19
    业余爱好者
    (一直觉得状态机是个非常高大上的东西,心中一直有疑问,今天才算是基本弄懂了。)
    对于一个全局对象的依赖,当做方法参数传递是个不错的设计。像之前提到的servlet中的过滤器的过滤方法中,参数就有FilterChain这一对象。一个方法需要依赖(广义)一个对象,无非来自于对象属性和方法自身。前者叫做组合,后者叫做依赖。在接口设计中,由于没有属性一说,所以只能通过参数传递了。这样看来,说是设计,实际上是不得已而为之啊(还能怎样啊)。
    一直分不清状态模式和观察者模式,两者不都是状态变化之后触发一定的动作吗?
    2020-03-30
    _1
    _3

  • 64 - 图20
    Amon Tin
    状态和事件都可能随需求增加,可以利用callback的方式,将这类组合逻辑注册到对应状态类。代码实现如下:
    /**

  • 事件枚举
    /
    public enum Event {
    A,B,C
    }
    /*

  • 事件回调
  • Created on 2021-06-24
    /
    public interface IEventCallback {
    void onEvent(StateMachine sm);
    }
    /*

  • 状态抽象类
    /
    public abstract class State {
    protected String name;
    protected Map events = new HashMap<>();
    public final void triggerEvent(Event e, StateMachine sm) {
    IEventCallback call = events.get(e);
    if (call != null) {
    call.onEvent(sm);
    }
    }
    }
    //S1状态类
    public class S1State extends State {
    private static final S1State instance = new S1State();
    Map events = new HashMap<>();
    public static S1State getInstance() {
    return instance;
    }
    //S1状态只关心A、B事件
    private S1State() {
    name = “S1”;
    events.put(Event.A, sm -> {
    sm.currentState = S2State.getInstance();
    sm.score++;
    });
    events.put(Event.B, sm -> {
    sm.score
    = 10;
    });
    }
    }
    //S2状态类
    public class S2State extends State {
    private static final S2State instance = new S2State();
    public static S2State getInstance() {
    return instance;
    }
    //S2状态只关心B、C事件
    private S2State() {
    name = “S2”;
    events.put(Event.B, sm -> {
    sm.score = 0;
    });
    events.put(Event.C, sm -> {
    sm.score—;
    sm.currentState = S1State.getInstance();
    });
    }
    }
    /**
  • 状态机核心类,状态流转的入口
    */
    public class StateMachine {
    int score;
    State currentState;
    public StateMachine() {
    score = 0;
    currentState = S1State.getInstance();
    }
    public void triggerAB() {
    currentState.triggerEvent(Event.A, this);
    currentState.triggerEvent(Event.B, this);
    }
    public void triggerBC() {
    currentState.triggerEvent(Event.B, this);
    currentState.triggerEvent(Event.C, this);
    }
    }
    2021-06-24
    _2
    _3
  • 64 - 图21
    阿骨打
    我跟老师的实现方法不太一样,关于最后的那个优化,我使用的是接口中的default方法,给状态类加一个default void setStateMachine(MarioStateMachine machine) {
    //留给子类实现
    } ,每个状态子类去实现它,就可以保证状态机的this可以传进来了。
    2、同时,之前 状态类中,new SuperMario(stateMachine) 的地方 都可以删掉了,改为工厂类存储的 状态类
    stateMachine.setCurrentState(MarioStateFactory.getByStateType(State.SUPER));
    3、状态机中: currentState.setStateMachine(this); // 每次改变状态类,顺便把自己传给状态类中。
    4、没改变几条代码 完成
    2020-09-30

    __2

  • 64 - 图22
    aoe
    查表法看上去很6
    2020-08-17
    _1
    _2

  • 64 - 图23
    攻城拔寨
    多写个抽象类默认实现接口,实现类继承抽象类就行了
    2020-03-31

    __2

  • 64 - 图24
    Frank
    打卡 今日学习状态模式,收获如下:
    状态模式通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑。与策略模式一样,状态模式可以解决if-else或着switch-case分支逻辑过多的问题。同时也了解到了有限状态机的概念,以前在看一些资料时遇到这个概念,之前不太理解这个状态机时干嘛用的,通过今天的学习,理解了状态机就是一种数学模型,该模型中有几个状态(有限的),在不同的场景下,不同的状态之间发生转移,在状态转移过程可能伴随着不同的事件发生。
    对于课堂讨论,有两种方法:1. 在实现类和接口中间定义一层中间类,中间类来实现接口,中间类中的方法都时空实现,实现类继承中间类,有选择性的覆写自己需要的方法。之后修改了接口,只需要修改中间类即可,这种方式引入了中间类,使类个数变多,一旦接口中的抽象方法变多,中间类相应的方法也随着变多,这种思路不是很优雅。2. 使用在模版模式那一节课中提到的回调方法。
    2020-03-30

    __2

  • 64 - 图25
    …?
    目前项目中的订单状态还是使用的第一层实现,大量if-else….
    2021-02-28

    __1

  • 64 - 图26
    亢(知行合一的路上)
    老师讲状态模式,对比三种实现,之前项目中用过方法三,也是在用方法一的过程中,觉得太繁琐了,容易出错后才使用的。但对方法二,没有使用过,竟然也不知道。自己学知识不全面,仅仅为了使用、解决眼前的问题,学到的非常肤浅,没能抓到本质。感谢老师!
    2020-04-01
    _1
    _1