• 状态机、状态模式


    什么是状态机?

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

    实现状态机的方法有多种,比较常用的有分支逻辑法、查表法、状态模式。

    我们以一个简单的 CD 播放器为例子。这个例子里面只有状态、事件,不包含动作

    简单CD播放器的按键与按键的功能
    按键 功能
    [Play/Pause] 播放/暂停
    [Stop] 停止

    状态迁移图:

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

    它的核心思想是根据状态迁移图,要么先确定状态、要么先确定事件,直译代码。

    方法分析:对于简单状态机,该法是可以接受的。但是,对于复杂的状态机,这种实现极易漏写或错写某个状态转移;代码中充斥大量if-else或switch-case 分支判断逻辑,可读性和可维护性差。

    如下就是先确定事件,然后再在事件内根据状态进行状态转移。

     1 typedef enum {
     2     ST_IDLE,
     3     ST_PLAY,
     4     ST_PAUSE
     5 } State;
     6 
     7 typedef enum {
     8     EV_PLAY_PAUSE,
     9     EV_STOP
    10 } Event;
    11 
    12 State state;
    13 
    14 // 初始化
    15 void initialize() {
    16     state = ST_IDLE;
    17 }
    18 
    19 // play or pause
    20 void playOrPause() {
    21     if (state == ST_IDLE) {
    22         state = ST_PLAY;
    23     } else if (state == ST_PLAY) {
    24         state = ST_PAUSE;
    25     } else if (state == ST_PAUSE) {
    26         state = ST_PLAY;
    27     }
    28 }
    29 
    30 // stop
    31 void stop() {
    32     if (state == ST_PLAY || state == ST_PAUSE) {
    33         state = ST_IDLE;
    34     }
    35 }
    36 
    37 // 事件响应
    38 void onEvent(Event ev) {
    39     switch (ev) {
    40     case EV_PLAY_PAUSE:
    41         playOrPause();
    42         break;
    43     case EV_STOP:
    44         stop();
    45         break;
    46     default:
    47         break;
    48     }
    49 }

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

    状态机除了用状态转移图表示外,还可以用二维表表示。如下第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态即执行的动作。

    CD播放器状态迁移表
    状态/事件 停止(EV_STOP) 播放/暂停(EV_PLAY_PAUSE)
    空闲(ST_IDLE) 忽略 开始播放并转为播放状态
    播放(ST_PLAY) 停止并转为空闲状态 停止并转为暂停状态
    暂停(ST_PAUSE) 停止并转为空闲状态 播放并转为播放状态

    相比于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,只需要修改transitionTable和actionTable两个二维数组即可。

     1 typedef enum {
     2     ST_IDLE,
     3     ST_PLAY,
     4     ST_PAUSE
     5 } State;
     6 
     7 typedef enum {
     8     EV_STOP,
     9     EV_PLAY_PAUSE
    10 } Event;
    11 
    12 State state;
    13 
    14 // transitionTable[i][j]表示状态为i时,发生事件j后,状态迁移到transitionTable[i][j]
    15 State transitionTable[][2] = {
    16         {ST_IDLE, ST_PLAY},
    17         {ST_IDLE, ST_PAUSE},
    18         {ST_IDLE, ST_PLAY}
    19 };
    20 
    21 // 初始化
    22 void initialize() {
    23     state = ST_IDLE;
    24 }
    25 
    26 State executeEvent(Event ev) {
    27     return transitionTable[state][ev];
    28 }
    29 
    30 // play or pause
    31 void playOrPause() {
    32     state = transitionTable[state][EV_PLAY_PAUSE];
    33 }
    34 
    35 // stop
    36 void stop() {
    37     state = transitionTable[state][EV_STOP];
    38 }
    39 
    40 // 事件响应
    41 void onEvent(Event ev) {
    42     switch (ev) {
    43     case EV_PLAY_PAUSE:
    44         playOrPause();
    45         break;
    46     case EV_STOP:
    47         stop();
    48         break;
    49     default:
    50         break;
    51     }
    52 }
    53 
    54 int main(void) {
    55     initialize();
    56     onEvent(EV_PLAY_PAUSE); // 播放
    57     printf("%d ", state);
    58     onEvent(EV_PLAY_PAUSE); // 暂停
    59     printf("%d ", state);
    60     onEvent(EV_STOP); // 停止
    61     printf("%d
    ", state);
    62 }

    执行结果为:1  2  0

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

    事件触发的动作如果非常简单,可以用二维的actionTable表示,则可以使用查表法。但是,如果执行的动作是一系列复杂的逻辑操作(比如加减积分、写数据库、发送消息等),就没有办法用简单的二维数组来表示了。

    参考资料

    1.64 | 状态模式:游戏、工作流引擎中常用的状态机是如何实现的? (geekbang.org)

    2.《C现代编程  集成开发环境、设计模式、极限编程、测试驱动开发、重构、持续集成》

  • 相关阅读:
    AWR报告-数据库概要信息(一)
    性能tips
    linux脚本随笔-01
    小和问题 和逆序对问题
    SpringMVC 学习笔记(拦截器的配置))
    删除eclipse Maven 进程 导致eclipse的workspace 启动不了
    SpringMVC 学习笔记(请求方法的返回值和参数)
    SpringMVC 学习笔记(处理器映射器的配置)
    (转)Maven 项目新建index.jsp报错问题
    MyBatis 学习总结(1)
  • 原文地址:https://www.cnblogs.com/yanxin880526/p/15115761.html
Copyright © 2020-2023  润新知