使用C++实现一套简单的状态机模型——原理解析

在上一文中,我们介绍了该状态机模型的使用方法。通过例子,我们发现可以使用该模型快速构建满足基本业务需求的状态机。本文我们将解析该模型的基础代码,以便大家可以根据自己状态机特点进行修改。(转载请指明出于breaksoftware的csdn博客)

该模板库的基础方法实现在之后给出的工程的AutoStateChart.h中,该文件一共215行,其中有16行是辅助调试代码。以上一文中状态机类为例:

class CMachine_Download_Run_App :
    public AutoStateChart::CAutoStateChartMachine<CMachine_Download_Run_App, CStoreofMachine>

CMachine_Download_Run_App类继承于模板类CAutoStateChartMachine,该模板类有两个参数:继承类自身和CStoreofMachine。CStoreofMachine类顾名思义,其是状态机中用于存储数据的类。为什么要设计这样的类?因为在我们的状态机模型中,每个基础状态都是割裂的。这样设计的一个好处便是我们可以让每个基础状态的逻辑代码独立,和其他模块没有任何耦合。当我们要删除某个状态时,我们只要将它从状态机的跳转声明中摘除即可。当我们要新增某个状态时,我们也只要在状态机跳转中做相应声明即可。但是往往优点也伴随着缺点:它使得每个基础状态类的数据交互产生了障碍。特别是没有上下文关系的基础状态,跳跃性的传递信息将变得非常困难。于是我们就需要一个存活于整个状态机声明周期的“数据库”,它可以被每个基础状态类访问和修改。于是CStoreofMachine就应运而生。因为该类比较独立,所以我们先从该类开始解析。首先我们看下该类的声明:

#pragma once
#include "AutoStateChart.h"

#define PROPERTY(type,name)													public:																			void Set##name(const type& n) {										    		m_##name = n;															}																			type Get##name() {																return m_##name;														}																			__declspec(property(get = Get##name, put = Set##name)) type Prop##name;	private:																		type m_##name;															

class CStoreofMachine{
	PROPERTY(std::string, ValueString);
	PROPERTY(std::wstring, ValueWString);
    PROPERTY(int, ValueInt);
};

该类的写法可能只适合于windows的vs平台,其他平台没论证过。其实它的内容是非常简单的,就是暴露成员变量的set和get方法。只是我觉得这种写法比较有意思,才在这儿罗列下。

我们再看下该类在模板中的使用,我们先从最基础的类开始解析

	class CEmpytLocalStore{};

	template<class Store = CEmpytLocalStore>
	class CLocalStoreAccess{
	public:
		typedef boost::function< Store& () > func;
		Store& GetStore(){return m_pFunc();};
		void SetStore(func& pCallback){m_pFunc = pCallback;};
	public:
		func m_pFunc;
	};

我们先定义了一个空类——CEmptyLocalStore,它相当于一个默认的“数据库”。当模板的使用者不需要“数据库”时,就可以在模板中不声明“数据库”类,此时我们的CEmptyLocalStore就生效了。比如我们上例的状态机可以改成:

class CMachine_Download_Run_App :
    public AutoStateChart::CAutoStateChartMachine<CMachine_Download_Run_App>

CLocalStoreAccess类主要提供如下作用:

  1. 设置访问“数据库”类对象的方法——SetStore
  2. 获取“数据库”类对象——GetStore

成员变量m_pFunc是一个函数指针,用于获取“数据库”类对象。该变量将由CLoaclStoreAccess继承类设置,相当于CLocalStoreAccess暴露了设置访问“数据库”类对象的能力。而它并不保存“数据库”类对象——它只提供“访问”能力,而不提供“存储”能力。

	template<class Store = CEmpytLocalStore>
	class CLocalStoreBase:
		public boost::enable_shared_from_this<CLocalStoreBase<Store>>,
		public CLocalStoreAccess<Store> {
	public:
		void Init(){ func pfunc = boost::bind(&CLocalStoreBase<Store>::_GetStore, shared_from_this()); SetStore(pfunc);};
	private:
		Store& _GetStore(){return m_Store;};
	private:
		Store m_Store;
	};

CLoaclStoreBase类的私有成员变量m_Store就是“数据库”类对象,即该类提供了“存储”功能。它继承于CLoaclStoreAccess类,使得该类具备了访问数据库的能力——虽然它的私有方法可以访问“数据库”类对象,但是我还是希望将这些能力分开。因为之后介绍的基础状态类要有“访问”的能力,而不应该具备“存储”的能力。如果不将这些能力进行拆分,将会导致层次结构混乱。

CLoaclStoreBase类的init方法,打通了和ClocalStoreAccess的关系——设置函数指针。

介绍完用于存储上下文的模板类后,我们现在可以关注下状态机相关的类了。我们先看上一文中一个基础状态类的例子

class CSimpleState_Download_From_A :
    public AutoStateChart::CAutoStateChartBase<CSimpleState_Download_From_A, CMachine_Download_Run_App, CStoreofMachine>

CSimpleState_Download_From_A类继承于CAutoStateChartBase模板类。第一个模板参数是继承类自身,第二个是它所属的状态机,第三个是“数据库”类。我们在看下CAutoStateChartBase类的声明

	template<class T, class MachineOrCompositeStates, class Store = CEmpytLocalStore>
	class CAutoStateChartBase:
		public boost::enable_shared_from_this<CAutoStateChartBase<T,MachineOrCompositeStates,Store>>,
		public CLocalStoreAccess<Store>
	{
		BOOST_TYPEOF_REGISTER_TYPE(T)
	public:
		std::string GetCurrentState(){ return typeid(T).name();};
		bool IsCompositeStates(){return false;};
		void SetInitState( const std::string& strState ){};

	public:
		virtual void Entry(){};
		virtual std::string Exit(){return "";};
	};

该模板类使用第一个模板参数类的类名作为其继承类的状态,并使用GetCurrentState方法提供获取功能。比如上例中的状态名为class CSimpleState_Download_From_A。这个模板类继承于CLocalStoreAccess模板类,使得继承类具有可以“访问”第三个模板参数类——“数据库”类的能力——不具备“存储”能力。同时该类还暴露了两个方法——Entry和Exit,他们分别用于在进出该状态时,让状态机调用。

状态和存储类都介绍完了,我们就剩下调度状态变化的状态机类和复合状态类。其实从某种程度上说,复合状态是一种简单的状态机,它们在很多地方存在共性。我们从状态机类入口,进行讲解。首先看下上一文中的例子

class CMachine_Download_Run_App :
    public AutoStateChart::CAutoStateChartMachine<CMachine_Download_Run_App, CStoreofMachine>

状态机类需要继承于CAutoStateChartMachine模板类,该类声明如下:

	template<class T, class LocalStore = CEmpytLocalStore>
	class CAutoStateChartMachine:
		public boost::enable_shared_from_this<CAutoStateChartMachine<T,LocalStore>>,
		public CLocalStoreAccess<LocalStore>
	{
	public:
		typedef LocalStore SelfStore;
		typedef T Self;
	public:
		CAutoStateChartMachine(){m_spStore.reset();};
		virtual ~CAutoStateChartMachine(){};
	private:
		virtual bool Transition(){return false;};
	public:
		void StartMachine() {
			if ( !m_spStore ) {
				m_spStore = boost::make_shared<CLocalStoreBase<LocalStore>>();
				m_spStore->Init();
				SetStore( m_spStore->m_pFunc );
			}
			while( Transition()){};
		};
	private:
		void Init(){};
	public:
		bool IsCompositeStates(){return false;};
	protected:
		std::string m_strCurrentState;
		std::string m_strCondition;
		MapString m_MapCompositeStatesSubState;
		boost::shared_ptr<CLocalStoreBase<LocalStore>> m_spStore;
	};

我们先看下这个类的成员变量。m_strCurrentState保存了状态机在跳转中的当前状态,m_strCondition保存了状态机中当前状态之前的状态的输出,它用于决定状态跳转方向。m_MapCompositeStatesSubState用于保存状态机中离开复合状态时的最后状态,即它记录复合状态机的浅历史。m_spStore指向“数据库”类对象。

该模板类最重要的函数就是StartMachine,它在第一次被调用时创建了“数据库”类对象。然后死循环调用Transition方法。Tansition方法是个虚方法,它是整个模型的核心。状态机类需要实现自己的Transition方法,以使得状态机可以运转起来。

我们再看下复合状态类的基础模板

	template<class T, class MachineOrCompositeStates, class LocalStore = CEmpytLocalStore>
	class CCompositeStates:
		public CAutoStateChartBase<T,MachineOrCompositeStates,LocalStore>{
		BOOST_TYPEOF_REGISTER_TYPE(T)
	public:
		CCompositeStates(){};
		~CCompositeStates(){};
	private:
		virtual bool Transition(){return false;};
	public:
		virtual void Entry(){while(Transition());};
		virtual std::string Exit(){return m_strCondition;};
	public:
		std::string GetCurrentState(){return m_strCurrentState;};
		bool IsCompositeStates(){return true;};
		void SetInitState( const std::string& strState ){ m_strCurrentState = strState; };
	protected:
		std::string m_strCurrentState;
		std::string m_strCondition;
		MapString m_MapCompositeStatesSubState;
	};

因为复合状态也是一种状态,所以它也要有Entry和Exit两种方法。而其Entry方法就是调用Transition方法。该模板类的Transition方法也是虚方法,这意味着继承于该模板类的方法也要去实现Transition。

于是所有的重心都集中于Transition方法的实现。

为了让代码美观,我参考了MFC中使用宏简洁代码的思路,设计了如下的宏:

#define STARTSTATE(state)															do {																				boost::shared_ptr<state> sp = boost::make_shared<state>();						sp->SetStore( m_pFunc );														if ( sp->IsCompositeStates() ) {													std::string strState = typeid(state).name();									BOOST_AUTO(it, m_MapCompositeStatesSubState.find(strState));					if ( m_MapCompositeStatesSubState.end() != it ) {									sp->SetInitState(it->second);													if ( DEBUGFRAMEFLAG ) {																std::string strInitState = it->second;											std::cout<<"CompositeStates SetInitState:"<<strInitState<<std::endl;				}																			}																			}																				sp->Entry();																	m_strCondition = sp->Exit();													if ( sp->IsCompositeStates() ) {													std::string strState = typeid(state).name();									std::string strInnerState = sp->GetCurrentState();								m_MapCompositeStatesSubState[strState] = strInnerState;							if ( DEBUGFRAMEFLAG ) {																std::cout<<"CompositeStates SaveState:"<<strState<< " " << strInnerState<< std::endl;				}																			}																			} while (0);
#define	REGISTERSTATECONVERTBEGIN(startstate)										bool Transition() {																	do {																				if ( m_strCurrentState.empty() ) {													m_strCurrentState = typeid(startstate).name();									STARTSTATE(startstate);															return true;																}																			}while(0);
#define REGISTERSTATECONVERT(fromstate,condition,tostate)								do {																				std::string strFromState = typeid(fromstate).name();							std::string strToState = typeid(tostate).name();								if ( DEBUGFRAMEFLAG	) {																std::cout<<"strFromState:"<<strFromState<<std::endl;								std::cout<<"condition:"<<condition<<std::endl;										std::cout<<"strToState:"<<strToState<<std::endl;									std::cout<<"m_strCurrentState:"<<m_strCurrentState<<std::endl;						std::cout<<"m_strCondition:"<<m_strCondition<<std::endl<<std::endl;				}																					if ( IsCompositeStates() ) {															if ( strFromState != m_strCurrentState													|| ( !m_strCondition.empty() && condition != m_strCondition ) ) { 						break;																		}																				}																					else {																					if ( strFromState != m_strCurrentState													|| condition != m_strCondition ) {													break;																			}																				}																					m_strCurrentState = strToState;													STARTSTATE(tostate);															return true;																}while(0);
#define REGISTERSTATECONVERTEND()														return false;																};	

然后复合状态类和状态机类只要使用这些宏去组织状态跳转,就可以清晰的描述过程了。

时间: 2024-12-29 06:59:45

使用C++实现一套简单的状态机模型——原理解析的相关文章

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

一般来说,"状态机"是一种表达状态转换变换逻辑的方法.曾经有人和我讨论过为什么不直接用ifelse,而要使用"状态机"去实现一些逻辑,认为使用"状态机"是一种炫技的表现.然而对于大型复杂逻辑的变化和跳转,使用ifelse将带来代码难以阅读等弊端.其实ifelse也是一种状态机实现的方式. 之前我们有个业务和操作系统有着强烈的关联,而我们希望比较清晰地描述整个业务中各个子业务的过程,就引入了状态机描述的方式.可是当时的状态机是使用if else方法

分享一套简单的CodeSmith三层模板

Model: <%@ Template Language="C#" TargetLanguage="C#" %> <%@ Assembly Name="SchemaExplorer" %> <%@ Import Namespace="SchemaExplorer" %> <%@ Property Name="SourceTable" Type="Schem

一套简单可依赖的Javascript库

还是[百度]的产品——Tangram不是我偏心,百度不是我亲戚这东西看上去确实不错 Tangram是一套简单可依赖的Javascript库,主要分为Base和Component两部分.Base提供了开发时常用功能的封装,是核心的工具库.Component是Tangram组件库,基于Tangram Base之上开发,提供各种UI组件和动画效果.为什么使用Tangram1.体积小巧,性能优良,使用简单.2.模块化架构,方便定制与扩展.3.适合团队开发,丰富的中文文档和本地技术优化,适合中国用户.4.

一套简单的交互信息(Session)服务开源套件正式上架

经过一段时间的努力,一套简单的交互信息(Session)服务开源套件终于开发到告一段落了. 套件使用简单的交互协议解决跨服务器的Http交互信息及协作 服务端内使用Sid-Item(Key-Value)的内存存储形式来维持交互信息的存储和提取 通讯协议 本服务端只为解决Http交互信息处理,因此协议相对简单只分为操作交互标识.设置和获取. 连接密码验证 使用PWD命令验证连接密码,使用$指定连接密码,客户端发送的命令如下: PWD\r\n $6\r\n 000000 服务端收到后则返回一个以+号

一个简单的MVC模型实现

function Event(sender) { this._sender = sender; this._listeners = []; } Event.prototype = { attach : function (listener) { this._listeners.push(listener); }, notify : function (args) { var index; for (index = 0; index < this._listeners.length; index

学习笔记(九)Filter 完成一个简单的权限模型 HttpServletWrapper 和 HttpServletResponseWrapper

2. HttpServletWrapper 和 HttpServletResponseWrapper 1). Servlet API 中提供了一个 HttpServletRequestWrapper 类来包装原始的 request 对象,HttpServletRequestWrapper 类实现了 HttpServletRequest 接口中的所有方法, 这些方法的内部实现都是仅仅调用了一下所包装的的 request 对象的对应方法 //包装类实现 ServletRequest 接口. publ

Linux-dns基础知识和BIND的简单配置-2(正向解析和反向解析)

DNS服务器基本配置 bind详解:包名:bind进程:named协议:dns使用端口:53(tcp,udp)相关包:bind-chroot:将named进程的活动范围限定在chroot目录,保证安全性.bind-devel:与开发相关的头文件和库文件(编译安装bind时所需)bind-libs:bind服务器端和客户端都使用到的公共库文件bind-utils : bind客户端工具程序文件:/usr/sbin/namedbind权限相关:安装完named会自动创建用户named系统用户,nam

套接字通信底层原理

套接字通信底层原理由应用程序内存拷贝到操作系统,操作系统遵循TCP协议向对方去发,对方接收到并发送信号 from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('127.0.0.1', 8081)) # 通信循环 while True: msg=input('>>: ').strip() #msg='' if len(msg) == 0:continue client.send(msg.encode(

学生党 应该去 研究研究 Socket(套接字) 实现原理

学生党 整天 不知所谓,    给你们 找点事 做做,     你们 可以去 研究一下 Socket (套接字) 的 实现原理, 看能不能 自己 实现一个  . Socket  是 操作系统 内核,  由 操作系统 直接调遣  .  为什么 是 操作系统 内核?  因为 Socket 涉及 到 IO,    IO 是 操作系统 的 基本任务, IO 涉及 中断, 所以必须作为 操作系统 内核,  由 操作系统 直接调度    . 多的也不要求了,     你们 去把  Windows Socke