使用C++实现一套简单的状态机模型——实例

原文地址:http://blog.****.net/breaksoftware/article/details/44042287

一般来说,“状态机”是一种表达状态转换变换逻辑的方法。曾经有人和我讨论过为什么不直接用ifelse,而要使用“状态机”去实现一些逻辑,认为使用“状态机”是一种炫技的表现。然而对于大型复杂逻辑的变化和跳转,使用ifelse将带来代码难以阅读等弊端。其实ifelse也是一种状态机实现的方式。

        之前我们有个业务和操作系统有着强烈的关联,而我们希望比较清晰地描述整个业务中各个子业务的过程,就引入了状态机描述的方式。可是当时的状态机是使用if else方法描述,显得整个过程比较臃肿,阅读起来也不够清晰。于是我尝试引入第三方的状态机库来重构这块的业务——比如boost里的状态机库。可是使用过程中感觉到了很多不便,索性自己动手实现一套清晰优雅的状态机模型。(转载请指明出于breaksoftware的****博客)

       编写模型之前,我们需要了解什么是状态机。我在搜索引擎上搜索到了若干结果,但是大部分都显得非常学术化。而实现一个大而全、包罗万象、放之四海而皆适宜的状态机模型也并非我的设计初衷。我设计的状态机具有如下特性:单线程、浅历史。单线程即我们的状态机是在一个线程内部运行的,不受外界其他线程干扰,这样我们在设计时就不用考虑多线程编程的问题。浅历史是状态机中的一个概念,它是指只记录最高一层复合状态的最后离开状态。这个特性如果有不了解的,可以先去搜索下。在实践中,该特性还是非常有用的。

        我们以一个简单、可能不恰当的例子来引入我这个状态机。我们先设计一个应用场景:给用户电脑安装软件并运行。这个场景我们可以拆分为如下几个逻辑:

  1. 检测是否安装
  2. 下载安装包
  3. 解压安装包并安装
  4. 运行

        这四个逻辑并不复杂,我们将其定义为基础状态——一种可以持续一段时间且内部执行逻辑我们不关心的状态。为了让这个逻辑变得稍微有点复杂,我们设计如下要求:

        对于未安装该软件的情况:

  • 从A地址下载安装包失败后从B地址下载
  • 从B地址下载安装包失败后从C地址下载
  • 从C地址下载安装包失败后认为执行失败
  • 下载成功后,检测CPU是否繁忙
  • CPU繁忙则继续检测CPU是否繁忙
  • CPU不繁忙则执行解压
  • 解压失败则重新下载。如果之前后A地址下载,则本次从B地址下载;如果之前从B地址下载,则本次从C地址下载
  • 解压成功后执行
  • 运行失败则重新下载。如果之前后A地址下载,则本次从B地址下载;如果之前从B地址下载,则本次从C地址下载
  • 运行成功则认为执行成功
        对于已安装该软件的情况:
  • 运行失败则先进行卸载,然后进入“未安装该软件”逻辑
  • 运行成功则认为执行成功

        我们以状态图来表示:

使用C++实现一套简单的状态机模型——实例

        图中“下载复合状态”是一个具有浅历史特性的复合状态;“安装后运行状态”是一个状态组合集,它让一组复杂的状态转换关系缩变成一种状态。这样如果其他地方需要复用该组合时,只要引入该组合状态名即可。

        我们从该模型使用者的角度去看如何去设计和编写代码,至于代码中的模板和函数可以先忽略掉,我们先了解其大概使用。

        从上图中我们可以确定有如下输出条件

  1. /* CondDefine.h 
  2. */  
  3. #pragma once  
  4.   
  5. // 是否安装  
  6. #define CONDITION_EXIST "CONDITION_EXIST"  
  7. #define CONDITION_NOEXIST "CONDITION_NOEXIST"  
  8.   
  9. // 下载是否成功  
  10. #define CONDITION_DOWNLOAD_SUC "CONDITION_DONWLOAD_SUC"  
  11. #define CONDITION_DOWNLOAD_FAI "CONDITION_DONWLOAD_FAI"  
  12.   
  13. // CPU是否繁忙  
  14. #define CONDITION_BUSY "CONDITION_BUSY"  
  15. #define CONDITION_NOBUSY "CONDITION_NOBUSY"  
  16.   
  17. // 解压是否成功  
  18. #define CONIDTION_UNZIP_SUC "CONIDTION_UNZIP_SUC"  
  19. #define CONDITION_UNZIP_FAI "CONDITION_UNZIP_FAI"  
  20.   
  21. // 运行是否成功  
  22. #define CONDITION_RUN_SUC "CONDITION_RUN_SUC"  
  23. #define CONDITION_RUN_FAI "CONDITION_RUN_FAI"  
  24.   
  25. // 安装是否成功  
  26. #define CONDITION_INSTALL_SUC "CONDITION_INSTALL_SUC"  
  27. #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地址下载”为例,查看该状态的基础代码
  1. #pragma once  
  2. #include "AutoStateChart.h"  
  3. #include "StoreMachine.h"  
  4.   
  5. // 引入输出宏  
  6. #include "CondDefine.h"  
  7.   
  8. // 引入业务基类  
  9. #include "Business_Random.h"  
  10.   
  11. class CMachine_Download_Run_App;     // 前置申明状态机类  
  12.   
  13. class CSimpleState_Download_From_A :  
  14.     public AutoStateChart::CAutoStateChartBase<CSimpleState_Download_From_A, CMachine_Download_Run_App, CStoreofMachine>  
  15. {  
  16. public:  
  17.     CSimpleState_Download_From_A(void) {};  
  18.     ~CSimpleState_Download_From_A(void){};  
  19. public:  
  20.     void Entry() {  
  21.     };  
  22.   
  23.     std::string Exit() {  
  24.         return CONDITION_DOWNLOAD_SUC;  
  25.     };  
  26. };  
        可以发现,该类非常简单。我们只要用模板申明好类(模板参数:自己、状态机类、存储类),并实现Entry和Exit两个函数就行了。我们再看下下载的复合状态类
  1. #pragma once  
  2. #include "AutoStateChart.h"  
  3. #include "StoreMachine.h"  
  4.   
  5. // 引入输出宏  
  6. #include "CondDefine.h"  
  7.   
  8. // 引入子状态  
  9. #include "SimpleState_Download_From_A.h"  
  10. #include "SimpleState_Download_From_B.h"  
  11. #include "SimpleState_Download_From_C.h"  
  12.   
  13. class CMachine_Download_Run_App;     // 前置申明状态机类  
  14.   
  15. // 该类将产生两种输出CONDITION_DONWLOAD_SUC、CONDITION_DONWLOAD_FAI  
  16. class CCompositeState_Download:  
  17.     public AutoStateChart::CCompositeStates<CCompositeState_Download, CMachine_Download_Run_App, CStoreofMachine>  
  18. {  
  19. public:  
  20.     CCompositeState_Download(void) {};  
  21.     ~CCompositeState_Download(void) {};  
  22. public:  
  23.     REGISTERSTATECONVERTBEGIN(CSimpleState_Download_From_A)  
  24.     REGISTERSTATECONVERT(CSimpleState_Download_From_A, CONDITION_DOWNLOAD_FAI, CSimpleState_Download_From_B)  
  25.     REGISTERSTATECONVERT(CSimpleState_Download_From_B, CONDITION_DOWNLOAD_FAI, CSimpleState_Download_From_C)  
  26.     REGISTERSTATECONVERTEND()  
  27. };  
        这个类也非常简单,它对应于图中的

使用C++实现一套简单的状态机模型——实例
        其中REGISTERSTATECONVERTBEGIN宏指定了该复合状态的起始状态(状态类),REGISTERSTATECONVERT指定了状态翻转逻辑(前状态类,条件,后状态类)

        我们再看下“安装后运行状态”这个组合状态的类

  1. #pragma once  
  2. #include "AutoStateChart.h"  
  3. #include "StoreMachine.h"  
  4.   
  5. // 引入输出宏  
  6. #include "CondDefine.h"  
  7.   
  8. // 引入子状态  
  9. #include "CompositeState_Download.h"  
  10. #include "SimpleState_Failed.h"  
  11. #include "SimpleState_CheckCPU.h"  
  12. #include "SimpleState_Unzip.h"  
  13. #include "SimpleState_Install.h"  
  14. #include "SimpleState_Run.h"  
  15. #include "SimpleState_Uninstall.h"  
  16. #include "SimpleState_Success.h"  
  17.   
  18. class CMachine_Download_Run_App;     // 前置申明状态机类  
  19.   
  20. class CCollectionState_Install_Run:  
  21.     public AutoStateChart::CCollectionStates<CCollectionState_Install_Run, CMachine_Download_Run_App, CStoreofMachine>  
  22. {  
  23. public:  
  24.     CCollectionState_Install_Run(void){};  
  25.     ~CCollectionState_Install_Run(void){};  
  26. public:  
  27.     REGISTERSTATECONVERTBEGIN(CCompositeState_Download)  
  28.     REGISTERSTATECONVERT(CCompositeState_Download, CONDITION_DOWNLOAD_SUC, CSimpleState_CheckCPU)  
  29.     REGISTERSTATECONVERT(CCompositeState_Download, CONDITION_DOWNLOAD_FAI, CSimpleState_Failed)  
  30.   
  31.     REGISTERSTATECONVERT(CSimpleState_CheckCPU, CONDITION_BUSY, CSimpleState_CheckCPU)  
  32.     REGISTERSTATECONVERT(CSimpleState_CheckCPU, CONDITION_NOBUSY, CSimpleState_Unzip)  
  33.   
  34.     REGISTERSTATECONVERT(CSimpleState_Unzip, CONIDTION_UNZIP_SUC, CSimpleState_Install)  
  35.     REGISTERSTATECONVERT(CSimpleState_Unzip, CONDITION_UNZIP_FAI, CCompositeState_Download)  
  36.   
  37.     REGISTERSTATECONVERT(CSimpleState_Install, CONDITION_INSTALL_SUC, CSimpleState_Run)  
  38.     REGISTERSTATECONVERT(CSimpleState_Install, CONDITION_INSTALL_FAI, CCompositeState_Download)  
  39.   
  40.     REGISTERSTATECONVERT(CSimpleState_Run, CONDITION_RUN_SUC, CSimpleState_Success)  
  41.     REGISTERSTATECONVERT(CSimpleState_Run, CONDITION_RUN_FAI, CSimpleState_Uninstall)  
  42.   
  43.     REGISTERSTATECONVERT(CSimpleState_Uninstall, "", CCompositeState_Download)  
  44.   
  45.     REGISTERSTATECONVERTEND()  
  46. };  
        该类的写法也很简单REGISTERSTATECONVERTBEGIN、REGISTERSTATECONVERT和REGISTERSTATECONVERTEND三个宏构成了整个状态跳转图

使用C++实现一套简单的状态机模型——实例
        最后我们再看下状态机的类

  1. #pragma once  
  2. #include "AutoStateChart.h"  
  3. #include "StoreMachine.h"  
  4.   
  5. // 引入输出宏  
  6. #include "CondDefine.h"  
  7.   
  8. // 引入子状态  
  9. #include "SimpleState_CheckExist.h"  
  10. #include "CollectionState_Install_Run.h"  
  11. #include "SimpleState_Run.h"  
  12. #include "SimpleState_Uninstall.h"  
  13. #include "SimpleState_Success.h"  
  14.   
  15. class CMachine_Download_Run_App :  
  16.     public AutoStateChart::CAutoStateChartMachine<CMachine_Download_Run_App, CStoreofMachine>  
  17. {  
  18. public:  
  19.     CMachine_Download_Run_App(void) {};  
  20.     ~CMachine_Download_Run_App(void) {};  
  21. public:  
  22.     REGISTERSTATECONVERTBEGIN(CSimpleState_CheckExist)  
  23.     REGISTERSTATECONVERT(CSimpleState_CheckExist, CONDITION_NOEXIST, CCollectionState_Install_Run)  
  24.     REGISTERSTATECONVERT(CSimpleState_CheckExist, CONDITION_EXIST, CSimpleState_Run)  
  25.   
  26.     REGISTERSTATECONVERT(CSimpleState_Run, CONDITION_RUN_FAI, CSimpleState_Uninstall)  
  27.     REGISTERSTATECONVERT(CSimpleState_Run, CONDITION_RUN_SUC, CSimpleState_Success)  
  28.   
  29.     REGISTERSTATECONVERT(CSimpleState_Uninstall, "", CCollectionState_Install_Run)  
  30.   
  31.     REGISTERSTATECONVERTEND()  
  32. };  
        它也是通过三个宏构成了整个逻辑跳转。

        在模块独立的前提下,该状态机还算是比较优雅简洁的展现了整个状态跳转的流程。当然在这个简洁的背后还是隐藏了很多背后的秘密。我们将在下节介绍其实现。

        我们最终通过如下代码,让整个状态机运行起来:

  1. boost::shared_ptr<CMachine_Download_Run_App> spc = boost::make_shared<CMachine_Download_Run_App>();  
  2. spc->StartMachine();