当前位置:   article > 正文

嵌入式C语言设计模式 - 状态机模式_状态机设计模式

状态机设计模式

概念:

状态机设计模式(State Machine Design Pattern)是一种行为型设计模式,用于管理对象的状态及其相应的行为。它将对象的行为和状态进行解耦,使得对象在不同状态下可以有不同的行为。

组成部分:

状态(State):表示对象所处的状态。每个状态都会定义一组可能的行为,并且这些行为只能在该状态下执行。

上下文(Context):包含状态机的主要逻辑,负责根据当前状态调用相应的行为。上下文还能够维护当前状态和处理状态转换。

事件(Event):触发状态转换的外部输入。当事件发生时,状态机会根据当前状态和事件类型执行相应的操作,可能导致状态的变化。

行为(Action):与每个状态相关联的具体操作。每个状态可以定义一组行为,用于在特定状态下执行相应的逻辑。

工作原理:

初始化:将对象的初始状态设置为合适的状态。

处理事件:当外部事件发生时,上下文将会根据当前状态和事件类型触发相应的行为。

状态转换:在行为执行后,状态机可能会根据一定的条件决定是否需要进行状态转换。状态转换可以将对象的状态从一个状态转变为另一个状态。

执行行为:根据新的状态,上下文将调用相应状态下定义的行为。

整理成大白话就是首先要定义状态,确定系统中可能存在的各个状态,并将每个状态抽象为一个独立的类,这些类实现了共同的接口,然后确定状态之间的转换规则,即在特定的条件下从一个状态转移到另一个状态。这些条件可以来自外部的事件、用户输入等。最后还需要创建一个上下文方法,该方法包含对当前状态的引用,并提供相应的方法来触发状态的转换。上下文方法负责根据转换规则决定状态的改变。

实践:

业务场景:有一个灯,按下开按钮,就会开灯,按下关按钮就会关灯

分析:

1、确定状态:只有开灯和关灯两种确定的状态

2、上下文:封装处理状态流转和执行状态改变后的行为

3、事件:把用户按下按钮视为触发事件

4、行为:执行状态改变后的方法,比如按下开灯按钮后需要去执行开灯的工作

  1. #include <stdio.h>
  2. // 状态枚举
  3. typedef enum {
  4. OPEN_STATE,
  5. CLOSE_STATE
  6. } State;
  7. // 状态机结构体
  8. typedef struct {
  9. State current_state;
  10. } LightStateMachine;
  11. // 上下文处理函数
  12. void light_fsm_event(LightStateMachine *machine) {
  13. if (machine->current_state == OPEN_STATE) {
  14. printf("正在关闭灯...\n");
  15. // todo 执行关灯行为
  16. machine->current_state = CLOSE_STATE;
  17. } else if (machine->current_state == CLOSE_STATE) {
  18. printf("正在开启灯...\n");
  19. // todo 执行开灯行为
  20. machine->current_state = OPEN_STATE;
  21. }
  22. }
  23. int main() {
  24. // 创建实例对象
  25. LightStateMachine machine;
  26. // 初始化为关闭状态
  27. machine.current_state = CLOSE_STATE;
  28. // 模拟按下按钮操作触发事件
  29. light_fsm_event(&machine); // 正在开启灯...
  30. light_fsm_event(&machine); // 正在关闭灯...
  31. return 0;
  32. }

但是这样代码会有点局限性,每增加一种状态就需要增加 if/else 来控制,不是很友好

改造如下:

  1. #include <stdio.h>
  2. // 状态枚举
  3. typedef enum {
  4. OPEN_STATE,
  5. CLOSE_STATE,
  6. // TODO: 添加其他状态
  7. STATE_COUNT
  8. } State;
  9. // 状态机结构体
  10. typedef struct {
  11. State current_state;
  12. } LightStateMachine;
  13. // 上下文处理函数类型定义
  14. typedef void (*StateHandler)(LightStateMachine *);
  15. // 开启状态处理函数
  16. void open_state_handler(LightStateMachine *machine) {
  17. printf("正在关闭灯...\n");
  18. // TODO: 执行关闭灯行为
  19. machine->current_state = CLOSE_STATE;
  20. }
  21. // 关闭状态处理函数
  22. void close_state_handler(LightStateMachine *machine) {
  23. printf("正在开启灯...\n");
  24. // TODO: 执行开启灯行为
  25. machine->current_state = OPEN_STATE;
  26. }
  27. // 状态处理函数映射表
  28. const StateHandler state_handlers[STATE_COUNT] = {
  29. open_state_handler,
  30. close_state_handler,
  31. // TODO: 添加其他状态处理函数
  32. };
  33. // 上下文处理函数
  34. void light_fsm_event(LightStateMachine *machine) {
  35. StateHandler handler = state_handlers[machine->current_state];
  36. handler(machine);
  37. }
  38. int main() {
  39. // 创建实例对象
  40. LightStateMachine machine;
  41. // 初始化为关闭状态
  42. machine.current_state = CLOSE_STATE;
  43. // 模拟按下按钮操作触发事件
  44. light_fsm_event(&machine); // 正在开启灯...
  45. light_fsm_event(&machine); // 正在关闭灯...
  46. return 0;
  47. }

状态机设计模式在嵌入式中还是很常见的设计模式,那我们能不能设计一套框架出来,以后适用的业务场景可以直接复用,提高效率呢?

通用框架:

思路:

这是一个有限状态机(Finite State Machine,FSM)的框架,某个对象的行为取决于其内部状态的变化,而这种状态的变化又是由外部事件的触发所引起的。

定义状态:确定需要的状态及其对应的枚举值,以 State 枚举类型表示

定义转换规则:确定状态之间的转换规则,即在某个状态下触发某个事件后,应该切换到哪个状态。将这些规则组织成一个转换规则数组,并定义为 TransitionRule 结构体数组

设计状态处理函数:根据每个状态的不同,为每个状态值定义相应的处理函数。这些处理函数将根据状态来执行相应的操作或逻辑

设计状态机数据结构:设计一个数据结构来存储状态机的相关信息,包括当前状态、转换规则和处理函数等

触发状态转换和处理:

  • 当外部事件触发时,调用有限状态机的状态转换函数,并传入触发事件所对应的状态值或事件参数。
  • 在状态转换函数中,根据当前状态和转换规则,自动地将状态切换到下一个状态,并调用对应的处理函数执行相应的操作
代码实现:

state_machine.h

  1. #ifndef STATE_MACHINE_H_
  2. #define STATE_MACHINE_H_
  3. #include <stdlib.h>
  4. /* 状态枚举 */
  5. typedef enum {
  6. STATE_1,
  7. STATE_2,
  8. STATE_3,
  9. /* 添加其他状态 */
  10. STATE_MAX
  11. } State;
  12. /* 事件枚举 */
  13. typedef enum {
  14. EVENT_1,
  15. EVENT_2,
  16. EVENT_3,
  17. /* 添加其他事件 */
  18. EVENT_MAX
  19. } Event;
  20. /* 状态处理函数签名,有两个参数:上下文指针和事件 */
  21. typedef void (*StateHandler)(void *context, Event event);
  22. /* 动作函数签名,有两个参数:上下文指针和事件 */
  23. typedef void (*Action)(void *context, Event event);
  24. /* 条件判断函数签名,有两个参数:上下文指针和事件,返回值为 int 类型 */
  25. typedef int (*Condition)(void *context, Event event);
  26. /* 状态转换规则结构体, 用于定义状态之间的转换规则 */
  27. typedef struct {
  28. State currentState; /* 当前状态 */
  29. Event event; /* 表示触发状态转换的事件 */
  30. Condition condition; /* 条件函数,用于判断是否满足执行该转换规则的条件 */
  31. Action action; /* 动作函数,用于执行状态转换时需要执行的动作 */
  32. State nextState; /* 转移到的下一个状态 */
  33. StateHandler handler; /* 状态处理函数,用于处理进入新状态后的操作 */
  34. } TransitionRule;
  35. /* 状态机结构体, 用于表示状态机本身,并存储状态机的状态和上下文数据 */
  36. typedef struct {
  37. State currentState; /* 当前状态 */
  38. TransitionRule *rules; /* 状态转换规则数组的指针,用于存储所有的状态转换规则 */
  39. size_t ruleCount; /* 状态转换规则的数量 */
  40. void *context; /* 指向状态机的上下文数据的指针 */
  41. StateHandler handlers[STATE_MAX]; /* 状态处理函数表,用于存储每个状态对应的处理函数 */
  42. } StateMachine;
  43. /* 初始化状态机, 用于初始化状态机。它接受转换规则数组、规则数量、上下文数据指针和状态处理函数数组作为参数,并将它们存储在 StateMachine 结构体中 */
  44. void state_machine_init(StateMachine *machine, TransitionRule *rules, size_t ruleCount, void *context, StateHandler *handlers);
  45. /* 执行状态机处理,用于处理事件。它接受一个事件作为参数,并根据当前状态以及转换规则判断是否需要执行状态转换和相关的动作 */
  46. void state_machine_handle_event(StateMachine *machine, Event event);
  47. /* 获取当前状态 */
  48. State state_machine_get_current_state(StateMachine *machine);
  49. #endif

state_machine.c

  1. #include "state_machine.h"
  2. /**
  3. * 初始化状态机
  4. *
  5. * @param machine 指向 StateMachine 结构体的指针
  6. * @param rules 状态转换规则数组
  7. * @param ruleCount 转换规则的数量
  8. * @param context 指向状态机上下文数据的指针
  9. * @param handlers 状态处理函数数组
  10. */
  11. void state_machine_init(StateMachine *machine, TransitionRule *rules, size_t ruleCount, void *context, StateHandler *handlers) {
  12. // 设置当前状态为第一个转换规则的当前状态
  13. machine->currentState = rules[0].currentState;
  14. // 存储转换规则数组、规则数量和上下文数据
  15. machine->rules = rules;
  16. machine->ruleCount = ruleCount;
  17. machine->context = context;
  18. // 将状态处理函数数组初始化为空
  19. for (size_t i = 0; i < STATE_MAX; i++) {
  20. machine->handlers[i] = NULL;
  21. }
  22. // 根据规则的当前状态,从处理函数数组中获取对应的处理函数并存储到状态处理函数数组中
  23. for (size_t i = 0; i < ruleCount; i++) {
  24. machine->handlers[machine->rules[i].currentState] = handlers[machine->rules[i].currentState];
  25. }
  26. }
  27. /* 执行状态机处理 */
  28. void state_machine_handle_event(StateMachine *machine, Event event) {
  29. for (size_t i = 0; i < machine->ruleCount; ++i) {
  30. /* 查找匹配的状态转换规则 */
  31. if (machine->currentState == machine->rules[i].currentState && event == machine->rules[i].event) {
  32. /* 检查条件判断 */
  33. if (machine->rules[i].condition(machine->context, event)) {
  34. /* 执行动作函数 */
  35. if (machine->rules[i].action != NULL) {
  36. machine->rules[i].action(machine->context, event);
  37. }
  38. /* 更新当前状态 */
  39. machine->currentState = machine->rules[i].nextState;
  40. /* 调用对应状态的处理函数 */
  41. if (machine->handlers[machine->currentState] != NULL) {
  42. machine->handlers[machine->currentState](machine->context, event);
  43. }
  44. break;
  45. } else {
  46. /* 处理条件不满足的情况 */
  47. // TODO: 错误处理代码
  48. break;
  49. }
  50. }
  51. }
  52. }
  53. /* 获取当前状态 */
  54. State state_machine_get_current_state(StateMachine *machine) {
  55. return machine->currentState;
  56. }
举例:

业务场景:按键长按时触发录音,接收到录音数据的同时把录音数据t发送到服务器,按键松手则停止录音

  1. #include <stdio.h>
  2. #include <stdbool.h>
  3. #include "state_machine.h"
  4. typedef enum {
  5. Idle,
  6. Recording,
  7. Stopped
  8. } RecorderState;
  9. typedef enum {
  10. ButtonPressed,
  11. ButtonReleased
  12. } Event;
  13. typedef struct {
  14. /* 录音相关数据 */
  15. bool isRecording;
  16. /* WebSocket 连接相关数据 */
  17. bool isConnected;
  18. /* 其他上下文数据 */
  19. // ...
  20. } RecorderContext;
  21. /* 动作函数的实现 */
  22. void start_recording(void *context, Event event) {
  23. RecorderContext *ctx = (RecorderContext *)context;
  24. ctx->isRecording = true;
  25. printf("Start recording\n");
  26. }
  27. void stop_recording(void *context, Event event) {
  28. RecorderContext *ctx = (RecorderContext *)context;
  29. ctx->isRecording = false;
  30. printf("Stop recording\n");
  31. }
  32. void send_data(void *context, Event event) {
  33. RecorderContext *ctx = (RecorderContext *)context;
  34. if (ctx->isConnected) {
  35. printf("Sending data to server\n");
  36. // Send recording data via WebSocket
  37. }
  38. }
  39. /* 条件判断函数的实现 */
  40. int is_button_pressed(void *context, Event event) {
  41. RecorderContext *ctx = (RecorderContext *)context;
  42. return event == ButtonPressed;
  43. }
  44. int is_button_released(void *context, Event event) {
  45. RecorderContext *ctx = (RecorderContext *)context;
  46. return event == ButtonReleased;
  47. }
  48. /* 状态处理函数的实现 */
  49. void idle_state_handler(void *context, Event event) {
  50. printf("Idle State\n");
  51. }
  52. void recording_state_handler(void *context, Event event) {
  53. printf("Recording State\n");
  54. }
  55. void stopped_state_handler(void *context, Event event) {
  56. printf("Stopped State\n");
  57. }
  58. int main() {
  59. /* 创建录音状态机的上下文数据 */
  60. RecorderContext recorderContext;
  61. recorderContext.isRecording = false;
  62. recorderContext.isConnected = true;
  63. /* 创建录音状态转换规则数组 */
  64. TransitionRule recorderRules[] = {
  65. {Idle, ButtonPressed, is_button_pressed, start_recording, Recording, recording_state_handler},
  66. {Recording, ButtonReleased, is_button_released, stop_recording, Stopped, stopped_state_handler},
  67. {Recording, ButtonPressed, is_button_pressed, send_data, Recording, NULL}
  68. };
  69. size_t recorderRuleCount = sizeof(recorderRules) / sizeof(recorderRules[0]);
  70. /* 创建录音状态机 */
  71. StateMachine recorderMachine;
  72. state_machine_init(&recorderMachine, recorderRules, recorderRuleCount, &recorderContext,
  73. (StateHandler[]){idle_state_handler, recording_state_handler, stopped_state_handler});
  74. /* 模拟按键事件 */
  75. printf("Initial state: %d\n", state_machine_get_current_state(&recorderMachine)); // 初始状态
  76. state_machine_handle_event(&recorderMachine, ButtonPressed); // 按键按下,开始录音
  77. state_machine_handle_event(&recorderMachine, ButtonPressed); // 按键继续按下,发送录音数据
  78. state_machine_handle_event(&recorderMachine, ButtonReleased); // 按键松开,停止录音
  79. state_machine_handle_event(&recorderMachine, ButtonPressed); // 按键按下,开始录音
  80. return 0;
  81. }

看完点赞收藏谢谢大家。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/696520
推荐阅读
相关标签
  

闽ICP备14008679号