使用C++实现一套简单的状态机模型——实例
一般来说,“状态机”是一种表达状态转换变换逻辑的方法。曾经有人和我讨论过为什么不直接用ifelse,而要使用“状态机”去实现一些逻辑,认为使用“状态机”是一种炫技的表现。然而对于大型复杂逻辑的变化和跳转,使用ifelse将带来代码难以阅读等弊端。其实ifelse也是一种状态机实现的方式。
之前我们有个业务和操作系统有着强烈的关联,而我们希望比较清晰地描述整个业务中各个子业务的过程,就引入了状态机描述的方式。可是当时的状态机是使用if else方法描述,显得整个过程比较臃肿,阅读起来也不够清晰。于是我尝试引入第三方的状态机库来重构这块的业务——比如boost里的状态机库。可是使用过程中感觉到了很多不便,索性自己动手实现一套清晰优雅的状态机模型。(转载请指明出于breaksoftware的csdn博客)
编写模型之前,我们需要了解什么是状态机。我在搜索引擎上搜索到了若干结果,但是大部分都显得非常学术化。而实现一个大而全、包罗万象、放之四海而皆适宜的状态机模型也并非我的设计初衷。我设计的状态机具有如下特性:单线程、浅历史。单线程即我们的状态机是在一个线程内部运行的,不受外界其他线程干扰,这样我们在设计时就不用考虑多线程编程的问题。浅历史是状态机中的一个概念,它是指只记录最高一层复合状态的最后离开状态。这个特性如果有不了解的,可以先去搜索下。在实践中,该特性还是非常有用的。
我们以一个简单、可能不恰当的例子来引入我这个状态机。我们先设计一个应用场景:给用户电脑安装软件并运行。这个场景我们可以拆分为如下几个逻辑:
- 检测是否安装
- 下载安装包
- 解压安装包并安装
- 运行
这四个逻辑并不复杂,我们将其定义为基础状态——一种可以持续一段时间且内部执行逻辑我们不关心的状态。为了让这个逻辑变得稍微有点复杂,我们设计如下要求:
对于未安装该软件的情况:
- 从A地址下载安装包失败后从B地址下载
- 从B地址下载安装包失败后从C地址下载
- 从C地址下载安装包失败后认为执行失败
- 下载成功后,检测CPU是否繁忙
- CPU繁忙则继续检测CPU是否繁忙
- CPU不繁忙则执行解压
- 解压失败则重新下载。如果之前后A地址下载,则本次从B地址下载;如果之前从B地址下载,则本次从C地址下载
- 解压成功后执行
- 运行失败则重新下载。如果之前后A地址下载,则本次从B地址下载;如果之前从B地址下载,则本次从C地址下载
- 运行成功则认为执行成功
对于已安装该软件的情况:
- 运行失败则先进行卸载,然后进入“未安装该软件”逻辑
- 运行成功则认为执行成功
我们以状态图来表示:
图中“下载复合状态”是一个具有浅历史特性的复合状态;“安装后运行状态”是一个状态组合集,它让一组复杂的状态转换关系缩变成一种状态。这样如果其他地方需要复用该组合时,只要引入该组合状态名即可。
我们从该模型使用者的角度去看如何去设计和编写代码,至于代码中的模板和函数可以先忽略掉,我们先了解其大概使用。
从上图中我们可以确定有如下输出条件
/* CondDefine.h
*/
#pragma once// 是否安装
#define CONDITION_EXIST "CONDITION_EXIST"
#define CONDITION_NOEXIST "CONDITION_NOEXIST"// 下载是否成功
#define CONDITION_DOWNLOAD_SUC "CONDITION_DONWLOAD_SUC"
#define CONDITION_DOWNLOAD_FAI "CONDITION_DONWLOAD_FAI"// CPU是否繁忙
#define CONDITION_BUSY "CONDITION_BUSY"
#define CONDITION_NOBUSY "CONDITION_NOBUSY"// 解压是否成功
#define CONIDTION_UNZIP_SUC "CONIDTION_UNZIP_SUC"
#define CONDITION_UNZIP_FAI "CONDITION_UNZIP_FAI"// 运行是否成功
#define CONDITION_RUN_SUC "CONDITION_RUN_SUC"
#define CONDITION_RUN_FAI "CONDITION_RUN_FAI"// 安装是否成功
#define CONDITION_INSTALL_SUC "CONDITION_INSTALL_SUC"
#define CONDITION_INSTALL_FAI "CONDITION_INSTALL_FAI"
整个状态跳转具有如下基础状态
基础状态 | 类 |
检测是否安装 | CSimpleState_CheckExist |
从A地址下载 | CSimpleState_Download_From_A |
从B地址下载 | CSimpleState_Download_From_B |
从C地址下载 | CSimpleState_Download_From_C |
检测CPU占用率 | CSimpleState_CheckCPU |
解压 | CSimpleState_Unzip |
安装 | CSimpleState_Install |
卸载 | CSimpleState_Uninstall |
执行成功 | CSimpleState_Success |
执行失败 | CSimpleState_Failed |
运行 | CSimpleState_Run |
我们以“从A地址下载”为例,查看该状态的基础代码
#pragma once
#include "AutoStateChart.h"
#include "StoreMachine.h"// 引入输出宏
#include "CondDefine.h"// 引入业务基类
#include "Business_Random.h"class CMachine_Download_Run_App; // 前置申明状态机类class CSimpleState_Download_From_A :public AutoStateChart::CAutoStateChartBase<CSimpleState_Download_From_A, CMachine_Download_Run_App, CStoreofMachine>
{
public:CSimpleState_Download_From_A(void) {};~CSimpleState_Download_From_A(void){};
public:void Entry() {};std::string Exit() {return CONDITION_DOWNLOAD_SUC;};
};
可以发现,该类非常简单。我们只要用模板申明好类(模板参数:自己、状态机类、存储类),并实现Entry和Exit两个函数就行了。我们再看下下载的复合状态类
#pragma once
#include "AutoStateChart.h"
#include "StoreMachine.h"// 引入输出宏
#include "CondDefine.h"// 引入子状态
#include "SimpleState_Download_From_A.h"
#include "SimpleState_Download_From_B.h"
#include "SimpleState_Download_From_C.h"class CMachine_Download_Run_App; // 前置申明状态机类// 该类将产生两种输出CONDITION_DONWLOAD_SUC、CONDITION_DONWLOAD_FAI
class CCompositeState_Download:public AutoStateChart::CCompositeStates<CCompositeState_Download, CMachine_Download_Run_App, CStoreofMachine>
{
public:CCompositeState_Download(void) {};~CCompositeState_Download(void) {};
public:REGISTERSTATECONVERTBEGIN(CSimpleState_Download_From_A)REGISTERSTATECONVERT(CSimpleState_Download_From_A, CONDITION_DOWNLOAD_FAI, CSimpleState_Download_From_B)REGISTERSTATECONVERT(CSimpleState_Download_From_B, CONDITION_DOWNLOAD_FAI, CSimpleState_Download_From_C)REGISTERSTATECONVERTEND()
};
这个类也非常简单,它对应于图中的
其中REGISTERSTATECONVERTBEGIN宏指定了该复合状态的起始状态(状态类),REGISTERSTATECONVERT指定了状态翻转逻辑(前状态类,条件,后状态类)。
我们再看下“安装后运行状态”这个组合状态的类
#pragma once
#include "AutoStateChart.h"
#include "StoreMachine.h"// 引入输出宏
#include "CondDefine.h"// 引入子状态
#include "CompositeState_Download.h"
#include "SimpleState_Failed.h"
#include "SimpleState_CheckCPU.h"
#include "SimpleState_Unzip.h"
#include "SimpleState_Install.h"
#include "SimpleState_Run.h"
#include "SimpleState_Uninstall.h"
#include "SimpleState_Success.h"class CMachine_Download_Run_App; // 前置申明状态机类class CCollectionState_Install_Run:public AutoStateChart::CCollectionStates<CCollectionState_Install_Run, CMachine_Download_Run_App, CStoreofMachine>
{
public:CCollectionState_Install_Run(void){};~CCollectionState_Install_Run(void){};
public:REGISTERSTATECONVERTBEGIN(CCompositeState_Download)REGISTERSTATECONVERT(CCompositeState_Download, CONDITION_DOWNLOAD_SUC, CSimpleState_CheckCPU)REGISTERSTATECONVERT(CCompositeState_Download, CONDITION_DOWNLOAD_FAI, CSimpleState_Failed)REGISTERSTATECONVERT(CSimpleState_CheckCPU, CONDITION_BUSY, CSimpleState_CheckCPU)REGISTERSTATECONVERT(CSimpleState_CheckCPU, CONDITION_NOBUSY, CSimpleState_Unzip)REGISTERSTATECONVERT(CSimpleState_Unzip, CONIDTION_UNZIP_SUC, CSimpleState_Install)REGISTERSTATECONVERT(CSimpleState_Unzip, CONDITION_UNZIP_FAI, CCompositeState_Download)REGISTERSTATECONVERT(CSimpleState_Install, CONDITION_INSTALL_SUC, CSimpleState_Run)REGISTERSTATECONVERT(CSimpleState_Install, CONDITION_INSTALL_FAI, CCompositeState_Download)REGISTERSTATECONVERT(CSimpleState_Run, CONDITION_RUN_SUC, CSimpleState_Success)REGISTERSTATECONVERT(CSimpleState_Run, CONDITION_RUN_FAI, CSimpleState_Uninstall)REGISTERSTATECONVERT(CSimpleState_Uninstall, "", CCompositeState_Download)REGISTERSTATECONVERTEND()
};
该类的写法也很简单REGISTERSTATECONVERTBEGIN、REGISTERSTATECONVERT和REGISTERSTATECONVERTEND三个宏构成了整个状态跳转图
最后我们再看下状态机的类
#pragma once
#include "AutoStateChart.h"
#include "StoreMachine.h"// 引入输出宏
#include "CondDefine.h"// 引入子状态
#include "SimpleState_CheckExist.h"
#include "CollectionState_Install_Run.h"
#include "SimpleState_Run.h"
#include "SimpleState_Uninstall.h"
#include "SimpleState_Success.h"class CMachine_Download_Run_App :public AutoStateChart::CAutoStateChartMachine<CMachine_Download_Run_App, CStoreofMachine>
{
public:CMachine_Download_Run_App(void) {};~CMachine_Download_Run_App(void) {};
public:REGISTERSTATECONVERTBEGIN(CSimpleState_CheckExist)REGISTERSTATECONVERT(CSimpleState_CheckExist, CONDITION_NOEXIST, CCollectionState_Install_Run)REGISTERSTATECONVERT(CSimpleState_CheckExist, CONDITION_EXIST, CSimpleState_Run)REGISTERSTATECONVERT(CSimpleState_Run, CONDITION_RUN_FAI, CSimpleState_Uninstall)REGISTERSTATECONVERT(CSimpleState_Run, CONDITION_RUN_SUC, CSimpleState_Success)REGISTERSTATECONVERT(CSimpleState_Uninstall, "", CCollectionState_Install_Run)REGISTERSTATECONVERTEND()
};
它也是通过三个宏构成了整个逻辑跳转。
在模块独立的前提下,该状态机还算是比较优雅简洁的展现了整个状态跳转的流程。当然在这个简洁的背后还是隐藏了很多背后的秘密。我们将在下节介绍其实现。
我们最终通过如下代码,让整个状态机运行起来:
boost::shared_ptr<CMachine_Download_Run_App> spc = boost::make_shared<CMachine_Download_Run_App>();spc->StartMachine();
相关文章:

net通过oledb 和ibm自带连接方式,连接db2数据库出错
第一种通过ibm方式连接 DataSet ds new DataSet(); OleDbConnection cn new OleDbConnection( "ProviderIBMDA400.1;Data Source192.168.21.10;User IDb4dd;" "Passwordb4dd;Default CollectionQIBMPP"); …

SAP QM 'QM System' 有什么控制作用?
SAP QM ‘QM System’ 有什么控制作用? QM system可以控制如下二点: 1>如果我方与Vendor的质量标准匹配,且相互认证,那么我方收货后不用检验,系统不产生检验批;如果我方与vendor的质量标准匹配&#x…

使用C++实现一套简单的状态机模型——原理解析
在上一文中,我们介绍了该状态机模型的使用方法。通过例子,我们发现可以使用该模型快速构建满足基本业务需求的状态机。本文我们将解析该模型的基础代码,以便大家可以根据自己状态机特点进行修改。(转载请指明出于breaksoftware的c…
干货:NIST评测(SRE19)获胜团队声纹识别技术分析 | CSDN博文精选
作者 | xjdier来源 | CSDN博文精选(*点击阅读原文,查看作者更多精彩文章)近日,NIST说话人识别技术评测 (Speaker Recognition Evaluation,SRE)正式公布榜单,芯片初创公司清微智能和清华大学等机构组成的联队…

网络系统传输负载测试
网络系统传输负载测试 随着企业各种信息系统相继投入使用,生产、管理信息逐步增加,企业网络规模迅速扩大,信息城域网承受着空前的压力,网络带宽严重不足,网络系统传输丢包、设备死机情况频频发生。我们需要对网络状况做…

android圆形旋转菜单,而对于移动转换功能支持
LZ该公司最近接手一个项目,需要写一个圆形旋转菜单,和菜单之间的移动换位支持,我本来以为这样的demo如若互联网是非常。想想你妈妈也帮不了我,空旋转,但它不能改变位置,所以LZ我们只能靠自己摸索。 最后LZ参…
微信9年:张小龙指明方向,微信AI全面开放NLP能力
作者 | 夕颜责编 | 王金许出品 | AI科技大本营(ID:rgznai100)一年一度的微信公开课 Pro 在广州保利世贸博览馆如期举行。一大早,同在博览馆举办的广州年货促展会参会者,夹杂着参加腾讯公开课的与会者,让这里变得人流攒…
实现HTTP协议Get、Post和文件上传功能——使用WinHttp接口实现
在《使用WinHttp接口实现HTTP协议Get、Post和文件上传功能》一文中,我已经比较详细地讲解了如何使用WinHttp接口实现各种协议。在最近的代码梳理中,我觉得Post和文件上传模块可以得到简化,于是几乎重写了这两个功能的代码。因为Get、Post和文…

第一篇文章,做个纪念
第一篇文章,做个纪念,这个blog好吗?拭目以待!转载于:https://blog.51cto.com/197536/88241

Maven工程引入jar包(转)
Maven项目引入jar包的方法,希望能帮助有需要的朋友们 法一.手动导入:项目右键—>Build Path—>Configure Build Path—>选中Libraries—>点击Add External Jars—>选中已事先下好的Jar包导入即可。 法二.通过pom.xml文件的Dependencies标…

实现HTTP协议Get、Post和文件上传功能——使用libcurl接口实现
之前我们已经详细介绍了WinHttp接口如何实现Http的相关功能。本文我将主要讲解如何使用libcurl库去实现相关功能。(转载请指明出于breaksoftware的csdn博客) libcurl在http://curl.haxx.se/libcurl/有详细的介绍,有兴趣的朋友可以去读下。本文…
32岁程序员,补偿N+2:“谢谢裁我,让我翻倍!” 网友:榜样!
2019年的冬天,“冷”的有些频繁。12月19日,《马蜂窝被曝裁员40% UGC模式变现难?》爆火,据悉马蜂窝将裁员40%,交易中心成了“重灾区”,赔偿N2,留下的除搜索推荐、内容中心等核心部门外࿰…

山有木兮木有枝,心悦君兮君不知
《越人歌》今夕何夕兮,搴舟中流。 今日何日兮,得与王子同舟 蒙羞被好兮,不訾诟耻 心几烦而不绝兮,得知王子 山有木兮木有枝,心悦君兮君不知。本是《夜宴》中的,"山有木兮木有枝,心悦君兮君…

浅析电商、社区、游戏常用的 MySQL 架构
一般、或者必须是这样、MySQL 架构一定要结合业务来分析、设计、优化 所以不管是那种架构、根据业务要求组合成符合需求的即是最好的、不能泛泛而谈 同时、也必须注意数据的安全(如ipsec,ssh,vpn传输) 常见的架构都是进行业务切…
基于Co-Attention和Co-Excitation的少样本目标检测 | NeurIPS 2019
「免费学习 60 节公开课:投票页面,点击讲师头像」作者 | VincentLee来源 | 晓飞的算法工程笔记(ID: gh_084c810bc839)导读:论文提出CoAE少样本目标检测算法,该算法使用non-local block来提取目标图片与查询…
服务器架设笔记——搭建用户注册和验证功能
之前介绍的Apache Httpd相关内容,都是些零散的知识点。而实际运用中,我们要根据不同的业务,将这些知识点连接起来以形成各种组合,来满足我们的需求。(转载请指明出于breaksoftware的csdn博客) 本文我将以用…

项目管理过程中应注意的问题
软件项目从角色分工方面可以划分为研发、开发和实施三类,每个类型的项目有各自的管理过程。下面笔者就公司实施类项目的经历,从项目经理的角度谈一谈实施类项目管理过程中应该注意的一些问题,希望大家共勉。确定项目概况俗话说:“…
原创jQuery移动设备弹出框插件——msgalert.js
最近开发经常会用到顶部弹出框,虽然有现成的(bootstrap等),但是都很臃肿,对于有些时候移动端活动页面有点大材小用。所以今晚花了20分钟写了一个通用的插件,我将其命名为msgalert.js。因为定位是jQuery插件,…
AbutionGraph:构建以知识图谱为核心的下一代数据中台
「免费学习 60 节公开课:投票页面,点击讲师头像」作者 | 图特摩斯科技创始人闭雨哲出品 | AI科技大本营(ID:rgznai100)前言图特摩斯科技(Thutmose)基于自研的图形数据库AbutionGraph(实时多维数…

服务器架设笔记——多模块和全局数据
随着项目工程的发展,多模块设计和性能优化是在所难免的。本文我将基于一些现实中可能遇到的需求,讲解如何在Apache的Httpd插件体系中实现这些功能。(转载请指明出于breaksoftware的csdn博客) 之前我碰到两个需求: 需要…

JSP学习笔记(七):使用JavaBean
bean.java publicclassB1 { publicString getString() { return"content"; } }page.jsp <%B1 b1 newB1(); out.print(b1.getString());%>

折返(Reentrancy)VS线程安全(Thread safety)
在Wiki上,折返例如,下面的定义(接) In computing, a computer program or subroutine is called reentrant if it can be interrupted in the middle of its execution and then safely called again ("re-entered") be…
服务器架设笔记——httpd插件支持mysql字符集选择
mysql数据库默认的字符集是latin1。默认情况下,我们编译的httpd插件是可以正常读取该类型的数据库,并且不会出现乱码。但是,如果我们的数据库变成其他格式,比如UTF8,那么默认读取出来的数据就是乱码,且无论…
只需3行代码自动生成高性能模型,支持4项任务,亚马逊发布开源库AutoGluon
作者 | KYLE WIGGERS编译 | AI科技大本营(ID:rgznai100)构建涉及图像、文本和表格数据集的机器学习应用并不容易。它需要特征工程或使用数据领域知识来创建使AI算法起作用的特征,还需要进行大量数据预处理,以确保训练模型时不会出…

在客户端(IE中)无法登录Citrix MetaFrame server的原因
当在IE中登录服务器时如果出现这面的错误提示: ERROR: The Citrix MetaFrame servers cannot process your request at this time. The Citrix XML Service object was not found. [404 Not Found] 我的原因是IE使用了8080端口,而我的Citrix XML servic…

虚拟化--015 配置VMware View Event database失败:
015 配置VMware View Event database失败:参考链接http://url.cn/VTq4zN 转载于:https://blog.51cto.com/williamliuwen/1686536
以金山界面库(openkui)为例思考和分析界面库的设计和实现——代码结构(完)
三年前,准备将金山界面库做一个全面的剖析。后来由于种种原因,这个系列被中断而一直没有更新。时过境迁,现在在windows上从事开发的人员越来越少,关注这块的技术的朋友也很少了。本以为这系列也随着技术的没落而不再被人所关注&am…
一包烟钱买到电动剃须刀,小米有品告诉你什么叫性价比
男人身上长得最快的是什么?答案是胡须。一名健康男性的胡须每天都要生长超过 0.4mm,比咱们头发的生长速度还快,这也是男人隔三差五就要剃须的原因之一。男人的一生是与胡子战斗一生,也是被剃须刀拖累的一生。出差办事儿࿰…

Label控件属性AssociatedControlID
可以使用Label控件来标注一个HTML表单字段。Label控件拥有属性AssociatedControlID,可以设置此属性来指向表示表单字段的ASP.NET控件。 例如,代码清单2-3中的页面含有一个简单的表单,表单包含两个字段用于输入名和姓。Label控件用于标注这两个TextBox控件。 Code<% Page Lan…

2015_8_21作业——有自翻译有复制他人的英语太差
date作用:打印或设置系统日期和时间格式:date [OPTION]...[FORMAT]date [u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]|是或 多选一的选项 ...代表同类内容可多次选项:注意短选项后不可加号-d,--dateSTRING显示时间字符串但不是立即:如date…