这个用c语言实现的状态机不错,基于设计模式

[复制链接]
9601|8
 楼主| keer_zu 发表于 2021-6-28 15:56 | 显示全部楼层 |阅读模式
这是别人实现的一个状态机例子,非常不错:@123654789 @ayb_ice @isbit 首先是很多人的实现方式:


  1. #include <stdio.h>

  2. void stopPlayer();
  3. void pausePlayer();
  4. void resumePlayer();
  5. void startPlayer();
  6. //按键的动作类型
  7. typedef enum {
  8.     EV_STOP,
  9.     EV_PLAY_PAUSE
  10. }EventCode;

  11. //MP3的状态
  12. enum{
  13.   ST_IDLE,
  14.   ST_PLAY,
  15.   ST_PAUSE
  16. };

  17. //MP3当前状态
  18. char state;

  19. //MP3状态初始化
  20. void init()
  21. {
  22.   state = ST_IDLE;
  23. }

  24. //状态机处理MP3的过程变化
  25. void onEvent(EventCode ec)
  26. {
  27.   switch (state)
  28.   {
  29.   case ST_IDLE:
  30.         if(EV_PLAY_PAUSE == ec)
  31.           startPlayer();
  32.         break;
  33.   case ST_PLAY:
  34.         if(EV_STOP == ec)
  35.           stopPlayer();
  36.         else if(EV_PLAY_PAUSE == ec)
  37.           pausePlayer();
  38.         break;
  39.   case ST_PAUSE:
  40.         if(EV_STOP == ec)
  41.           stopPlayer();
  42.         else if(EV_PLAY_PAUSE == ec)
  43.           resumePlayer();
  44.         break;
  45.   default:
  46.         break;
  47.   }
  48. }

  49. void stopPlayer()
  50. {
  51.   state = ST_IDLE;
  52.   printf("停止播放音乐\n");
  53. }

  54. void pausePlayer()
  55. {
  56.   state = ST_PAUSE;
  57.   printf("暂停播放音乐\n");
  58. }

  59. void resumePlayer()
  60. {
  61.   state = ST_PLAY;
  62.   printf("恢复播放音乐\n");
  63. }

  64. void startPlayer()
  65. {
  66.   state = ST_PLAY;
  67.   printf("开始播放音乐\n");
  68. }
  69. //主程序实现MP3的播放控制
  70. void main()
  71. {
  72.   init();
  73.   onEvent(EV_PLAY_PAUSE);//播放
  74.   onEvent(EV_PLAY_PAUSE);//暂停
  75.   onEvent(EV_PLAY_PAUSE);//继续播放
  76.   onEvent(EV_STOP);      //停止
  77. }


值得学习的实现方式:
  1. #include <stdio.h>

  2. /***********************************************
  3. 1、定义状态接口,以MP3的状态接口为例,每种状态下都可能发生
  4. 两种按键动作。
  5. ************************************************/
  6. typedef struct State{
  7.   void (* stop)();
  8.   void (* palyOrPause)();
  9. }State;
  10. /***********************************************
  11. 2、定义系统当前状态指针,保存系统的当前状态
  12. ************************************************/
  13. State * pCurrentState;
  14. /***********************************************
  15. 3、定义具体状态,根据状态迁移图来实现具体功能和状态切换。
  16. ************************************************/
  17. void ignore();
  18. void startPlay();
  19. void stopPlay();
  20. void pausePlay();
  21. void resumePlay();
  22. //空闲状态时,stop键操作无效,play/pause会开始播放音乐
  23. State IDLE = {
  24.   ignore,
  25.   startPlay
  26. };
  27. //播放状态时,stop键会停止播放音乐,play/pause会暂停播放音乐
  28. State PLAY = {
  29.   stopPlay,
  30.   pausePlay
  31. };
  32. //暂停状态时,stop键会停止播放音乐,play/pause会恢复播放音乐
  33. State PAUSE = {
  34.   stopPlay,
  35.   resumePlay
  36. };
  37. void ignore()
  38. {
  39.   //空函数,不进行操作
  40. }
  41. void startPlay()
  42. {
  43.   //实现具体功能
  44.   printf("开始播放音乐\n");
  45.   //进入播放状态
  46.   pCurrentState = &PLAY;
  47. }
  48. void stopPlay()
  49. {
  50.   //实现具体功能
  51.   printf("停止播放音乐\n");
  52.   //进入空闲状态
  53.   pCurrentState = &IDLE;
  54. }
  55. void pausePlay()
  56. {
  57.   //实现具体功能
  58.   printf("暂停播放音乐\n");
  59.   //进入暂停状态
  60.   pCurrentState = &PAUSE;
  61. }
  62. void resumePlay()
  63. {
  64.   //实现具体功能
  65.   printf("恢复播放音乐\n");
  66.   //进入播放状态
  67.   pCurrentState = &PLAY;
  68. }
  69. /***********************************************
  70. 4、定义主程序上下文操作接口,主程序只关心当前状态,不关心状态之间
  71. 是怎么变化的。
  72. ************************************************/
  73. void onStop();
  74. void onPlayOrPause();
  75. State context = {
  76.   onStop,
  77.   onPlayOrPause
  78. };
  79. void onStop(State *pThis)
  80. {
  81.   pCurrentState->stop(pThis);
  82. }

  83. void onPlayOrPause(State *pThis)
  84. {
  85.   pCurrentState->palyOrPause(pThis);
  86. }
  87. /***********************************************
  88. 5、初始化系统当前状态指针,其实就是指定系统的起始状态
  89. ************************************************/
  90. void init()
  91. {
  92.   pCurrentState = &IDLE;
  93. }
  94. /***********************************************
  95. 6、主程序通过上下文操作接口来控制系统当前状态的变化
  96. ************************************************/
  97. void main()
  98. {
  99.   init();
  100.   context.palyOrPause();//播放
  101.   context.palyOrPause();//暂停
  102.   context.palyOrPause();//播放
  103.   context.stop();//停止
  104. }




 楼主| keer_zu 发表于 2021-6-28 16:00 | 显示全部楼层
@123654789 @ayb_ice @isbit
对比前后两份代码,六步法实现的状态机比简单状态机明显有以下几方面的优点:

代码结构要更加清晰,避免了过多的switch...case或者if...else语句   的使用。

很好地体现了开闭原则和单一职责原则,每个状态都是一个子结构体,你要增加状态就要增加子结构体,你要修改状态,你只修改一个子结构体就可以了。

封装性非常好,状态变换放置到子结构体的内部来实现,外部的调用不用知道子结构体的内部如何实现状态和行为的变换。

最后跟大家总计一下状态机六步法:

(1)、定义状态接口。
(2)、定义系统当前状态指针。
(3)、定义具体状态,根据状态迁移图来实现具体功能和状态切换。
(4)、定义主程序上下文操作接口。
(5)、初始化系统当前状态指针。
(6)、主程序通过上下文操作接口来控制系统当前状态的变化。

一般来说,熟练使用状态机六步法的嵌入式开发者,大都是两年软件开发经验以上的老鸟了。所以,如果你还是个嵌入式新手,请在实际开发中多多运用它,以后你的代码才能越来越优雅美观。而且掌握状态机编程对理解其他更复杂的设计模式也是大有裨益的。
 楼主| keer_zu 发表于 2021-6-28 16:02 | 显示全部楼层
 楼主| keer_zu 发表于 2021-6-28 16:03 | 显示全部楼层
核心思路:我们可以利用C语言的多态特性来分解复杂的条件分支(关于c语言多态的实现,请查看c语言面向对象基础)。这样一来可以就避免大量的swith...case和 if...else等条件分支语句,提高程序的可维护性和可扩展性。
isbit 发表于 2021-7-20 09:51 | 显示全部楼层
记住了,一点要多使用状态机,不然程序很乱;
 楼主| keer_zu 发表于 2021-7-20 10:08 | 显示全部楼层
是的,状态机用起来,状态机就用状态模式来实现。
 楼主| keer_zu 发表于 2021-11-4 10:54 | 显示全部楼层
继续状态模式
不动卿 发表于 2022-2-7 14:27 | 显示全部楼层
恕我愚笨,c语言基础比较差,请问大佬第二个六步法里面用到的结构体和函数指针可以细说一下吗?比如最后的context.palyOrPause();这句,context是个结构体吧,可是他里面没有palyOrPause呀,看不懂欸这里
 楼主| keer_zu 发表于 2022-2-7 16:32 | 显示全部楼层
不动卿 发表于 2022-2-7 14:27
恕我愚笨,c语言基础比较差,请问大佬第二个六步法里面用到的结构体和函数指针可以细说一下吗?比如最后的c ...

cotex是个结构体变量,就是 State。所以对它的成员函数的调用也是变量名->stop()和变量名->playOrPause()。

这里的变量是指针变量。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:qq群:49734243 Email:zukeqiang@gmail.com

1488

主题

12949

帖子

55

粉丝
快速回复 在线客服 返回列表 返回顶部