设计模式:状态模式
状态机有3个要素:状态,事件,动作。
假如一个对象有3个状态:S1、S2、S3。影响状态的事件有3个:E1、E2、E3。每个状态下收到对应事件的时候,对象的动作为AXY。那么该对象的状态机就可以用如下表格来表示。S1收到事件E1的时候动作为A11,收到事件E2的时候动作为A12,收到事件E3的时候动作为A13,以此类推。
| E1 | E2 | E3 | |
| S1 | A11 | A12 | A13 |
| S2 | A21 | A22 | A23 |
| S3 | A31 | A32 | A33 |
其中,动作可以是状态发生切换,也可以是其它与业务有关的动作。
1分支逻辑法
分支逻辑法,是最简单 、最直观,也是最常用的一种方法。
如下是分支逻辑法的实现:
if(state == S1) {
if (event == E1) {
A31();
} else if (event == E2) {
A32();
} else {
A33();
}
} else if (state == S2) {
if (event == E1) {
A31();
} else if (event == E2) {
A32();
} else {
A33();
}
} else {
if (event == E1) {
A31();
} else if (event == E2) {
A32();
} else {
A33();
}
}
在linux内核中,tcp有自己的状态机,如下代码,就是在收到tcp报文时,其中的一小段状态机代码,这段代码就是使用分支逻辑法。
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
...
switch (sk->sk_state) {
case TCP_CLOSE:
goto discard;
case TCP_LISTEN:
if (th->ack)
return 1;
if (th->rst)
goto discard;
if (th->syn) {
if (th->fin)
goto discard;
/* It is possible that we process SYN packets from backlog,
* so we need to make sure to disable BH and RCU right there.
*/
rcu_read_lock();
local_bh_disable();
acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;
local_bh_enable();
rcu_read_unlock();
if (!acceptable)
return 1;
consume_skb(skb);
return 0;
}
goto discard;
case TCP_SYN_SENT:
tp->rx_opt.saw_tstamp = 0;
tcp_mstamp_refresh(tp);
queued = tcp_rcv_synsent_state_process(sk, skb, th);
if (queued >= 0)
return queued;
/* Do step6 onward by hand. */
tcp_urg(sk, skb, th);
__kfree_skb(skb);
tcp_data_snd_check(sk);
return 0;
}
...
}
2查表法
如果状态发生改变,比如增删,或者事件发生改变,比如增加一个事件或者删除一个事件,就需要对代码进行修改。分支逻辑法不满足开闭原则。
查表法使用两个两级map:
①transitionTable
第一级map的key为状态,value是第二级map;第二级map的key是事件类型,value是目标状态。
②actionTable
第一级map的key为状态类型,value是第二级map;第二级map的key是事件类型,value是action。
如下是查表法的示意代码:
#include <iostream>
#include <map>
#include <functional>
#include <stdexcept> // 用于异常处理enum class State : int32_t {S1,S2,S3
};enum class Event : int32_t {E1,E2,E3
};class StateMachine {
public:StateMachine(State const& initialState) : state_{initialState} {// 初始化状态转移表transitionTable_ = {{State::S1, {{Event::E1, State::S1}, {Event::E2, State::S2}, {Event::E3, State::S3}}},{State::S2, {{Event::E1, State::S1}, {Event::E2, State::S3}, {Event::E3, State::S2}}},{State::S3, {{Event::E1, State::S3}, {Event::E2, State::S2}, {Event::E3, State::S3}}}};// 初始化动作表(修复 lambda 捕获和逗号分隔)actionTable_ = {{State::S1, {{Event::E1, [this]() { std::cout << "action11\n"; }},{Event::E2, [this]() { std::cout << "action12\n"; }},{Event::E3, [this]() { std::cout << "action13\n"; }}}},{State::S2, {{Event::E1, [this]() { std::cout << "action21\n"; }},{Event::E2, [this]() { std::cout << "action22\n"; }},{Event::E3, [this]() { std::cout << "action23\n"; }}}},{State::S3, {{Event::E1, [this]() { std::cout << "action31\n"; }},{Event::E2, [this]() { std::cout << "action32\n"; }},{Event::E3, [this]() { std::cout << "action33\n"; }}}}};}void OnEvent(Event const& event) {// 检查当前状态和事件是否有效if (actionTable_.find(state_) == actionTable_.end() ||actionTable_[state_].find(event) == actionTable_[state_].end()) {throw std::runtime_error("Invalid state or event!");}// 执行动作并更新状态actionTable_[state_][event](); // 调用函数state_ = transitionTable_[state_][event];}State GetState() {return state_;}private:State state_;std::map<State, std::map<Event, State>> transitionTable_;std::map<State, std::map<Event, std::function<void()>>> actionTable_;
};int main() {StateMachine sm{State::S1};sm.OnEvent(Event::E3); // 修复拼写错误if (State::S3 != sm.GetState()) {std::cout << "state " << (int32_t)sm.GetState() << " is not expected\n";}sm.OnEvent(Event::E2);if (State::S2 != sm.GetState()) {std::cout << "state " << (int32_t)sm.GetState() << " is not expected\n";}return 0;
}
查表法相对于分支逻辑法 ,将状态机创建的代码和状态机转换的代码进行了解耦。如果状态有改变或者事件有改变,那么可以修改StateMachine的构造函数,而不需要修改OnEvent函数。
transitionTable_和actionTable_是数据结构,可以看作存储,OnEvent中的代码逻辑可以看作计算逻辑。解耦就是将存储和计算逻辑代码进行分离,将可以修改的部分和不可以修改的部分进行分离。
相关文章:
设计模式:状态模式
状态机有3个要素:状态,事件,动作。 假如一个对象有3个状态:S1、S2、S3。影响状态的事件有3个:E1、E2、E3。每个状态下收到对应事件的时候,对象的动作为AXY。那么该对象的状态机就可以用如下表格来表示。S1收到事件E1的…...
【故障处理】- 执行命令crsctl query crs xxx一直hang
【故障处理】- 执行命令crsctl query crs xxx一直hang 一、概述二、故障处理三、解决方法 一、概述 Oracle RAC环境中,遇到执行crsctl query crs xxx等相关命令不返回任何结果,一直hang在那里。系统下执行命令ps -ef |grep crsctl query crs softwarever…...
Zabbix——监控Nginx
背景 在项目中使用Nginx之后,有时候我们需要知道Nginx具体的工作情况,这时候就需要使用zabbix进行Nginx的相关监控 这边我们有两种方法 使用普通的http请求的方式获取基本信息如果使用了Nginx Plus,就可以通过Nginx Plus的接口获取更多的信…...
开源工具推荐--思维导图、流程图等绘制
1. 前言 在工作中,经常要用到各种不同的工具,随着系统的升级,有些工具也在不断更新升级。这里收集整理一些好用的开源工具推荐,遵循以下一些基本原则:开源免费,商业工具的有效平替,轻量级&…...
【论文笔记】Transformer^2: 自适应大型语言模型
Code repo: https://github.com/SakanaAI/self-adaptive-llms 摘要 自适应大型语言模型(LLMs)旨在解决传统微调方法的挑战,这些方法通常计算密集且难以处理多样化的任务。本文介绍了Transformer(Transformer-Squared)…...
FFmpeg源码:av_strlcpy函数分析
一、引言 在C/C编程中经常会用到strcpy这个字符串复制函数。strcpy是C/C中的一个标准函数,可以把含有\0结束符的字符串复制到另一个地址空间。但是strcpy不会检查目标数组dst的大小是否足以容纳源字符串src,如果目标数组太小,将会导致缓冲区…...
Unity Shader学习6:多盏平行光+点光源 ( 逐像素 ) 前向渲染 (Built-In)
0 、分析 在前向渲染中,对于逐像素光源来说,①ForwardBase中只计算一个平行光,其他的光都是在FowardAdd中计算的,所以为了能够渲染出其他的光照,需要在第二个Pass中再来一遍光照计算。 而有所区别的操作是࿰…...
docker批量pull/save/load/tag/push镜像shell脚本
目录 注意: 脚本内容 执行效果 注意: 以下脚本为shell脚本通过docker/nerdctl进行镜像独立打包镜像的相关操作脚本内仓库信息和镜像存取路径需自行更改需自行创建images.txt并填写值,并且与脚本位于同级目录下 [rootmaster01 sulibao]# l…...
五十天精通硬件设计第32天-S参数
系列文章传送门 50天精通硬件设计第一天-总体规划-CSDN博客 目录 1. S参数基础 2. S参数在信号完整性中的作用 3. 单端 vs. 差分S参数 4. S参数的关键特性 5. S参数的获取与使用 6. S参数分析中的常见问题 7. 实际案例:PCIe通道分析 8. 工具推荐 总结 信号完整性中…...
6.2.4 基本的数据模型
文章目录 基本的数据模型 基本的数据模型 基本的数据模型包含层次模型,网状模型和关系模型。 层次模型:使用树型结构表示数据间联系。记录间的联系用指针实现,简单高效。但是只能表示1:n的联系,且对插入、删除的限制多。网状模型…...
DeepSeek ,银行营销会被 AIGC 颠覆吗?
AI 让银行营销更智能,但更重要的是“懂客户” AI 在银行营销中的应用已经不仅仅局限于文案生成,而是渗透到了整个营销流程。 据悉,中国银行已经开始利用 AI 大模型构建智能营销助手系统,结合知识图谱和 AI 技术,实现…...
第150场双周赛:好数字之和、分割正方形 Ⅰ、分割正方形 Ⅱ、最短匹配字符串
Q1、好数字之和 1、题目描述 给定一个整数数组 nums 和一个整数 k,如果元素 nums[i] 严格 大于下标 i - k 和 i k 处的元素(如果这些元素存在),则该元素 nums[i] 被认为是 好 的。如果这两个下标都不存在,那么 nums…...
HDFS是如何存储和管理大数据
HDFS(Hadoop Distributed File System,Hadoop分布式文件系统)是专为大数据处理而设计的分布式文件系统,具有高吞吐量、高容错性等特点,适用于大规模数据存储和管理。以下是HDFS存储和管理大数据的详细机制:…...
进阶——第十六届蓝桥杯嵌入式熟练度练习(开发板捕获频率和占空比)
单通道捕获频率 HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {if(htim->InstanceTIM2) {cap1HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);TIM2-&…...
智能协同:数据集成平台与DeepSeek驱动的数据分析与智能调度革新
前言 企业面临着海量数据的挑战与机遇。如何高效地整合多源数据、精准分析并智能决策,成为企业提升竞争力的关键。本文解析轻易云数据集成平台与DeepSeek技术结合在数据分析和智能调度方面的创新应用,揭示其为企业带来的高效、智能与精准的业务价值。 …...
Mybatis高级(动态SQL)
目录 一、动态SQL 1.1 数据准备: 1.2 <if>标签 1.3<trim> 标签 1.4<where>标签 1.5<set>标签 1.6 <foreach>标签 1.7<include> 标签 一、动态SQL 动态SQL是Mybatis的强⼤特性之⼀,能够完成不同条件下不同…...
申论对策建议类【2022江苏B卷第一题“如何开展网络直播”】
材料: 近年来,公安交管部门通过网络直播,将执法过程和执法细节以视频形式呈现在公众面前,吸引“围观”、组织点评,让执法过程变成一堂生动的法治公开课。 “各位网友,大家好!这里是‘全国交通…...
蓝耘智算携手DeepSeek,共创AI未来
🌟 各位看官号,我是egoist2023! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习如何通过蓝耘智算使用DeepSeek R1模型 👍 如果觉得这篇文章有帮助,欢迎您一键三连&a…...
FFmpeg源码:url_find_protocol函数分析
一、url_find_protocol函数的定义 url_find_protocol函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/avio.c中: static const struct URLProtocol *url_find_protocol(const char *filename) {const URLProt…...
3D与2D机器视觉机械臂引导的区别
3D与2D机器视觉在机械臂引导中的主要区别如下: 数据维度 2D视觉:仅处理平面图像,提供X、Y坐标信息,无法获取深度(Z轴)数据。 3D视觉:处理三维空间数据,提供X、Y、Z坐标及物体的姿态…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
