在上一文中,我們介紹了該狀態機模型的使用方法。通過例子,我們發現可以使用該模型快速構建滿足基本業務需求的狀態機。本文我們將解析該模型的基礎程式碼,以便大家可以根據自己狀態機特點進行修改。(轉載請指明出於breaksoftware的csdn部落格)
該模板庫的基礎方法實現在之後給出的工程的AutoStateChart.h中,該檔案一共215行,其中有16行是輔助除錯程式碼。以上一文中狀態機類為例:
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)\t\t\t\t\t\t\t\t\t\t\t\t\t\\public:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\tvoid Set##name(const type& n) {\t\t\t\t\t\t\t\t\t\t \\\t\tm_##name = n;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\ttype Get##name() {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\treturn m_##name;\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t__declspec(property(get = Get##name, put = Set##name)) type Prop##name;\t\\private:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\ttype m_##name;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\ class CStoreofMachine{\tPROPERTY(std::string, ValueString);\tPROPERTY(std::wstring, ValueWString); PROPERTY(int, ValueInt);}
該類的寫法可能只適合於windows的vs平臺,其他平臺沒論證過。其實它的內容是非常簡單的,就是暴露成員變數的set和get方法。只是我覺得這種寫法比較有意思,才在這兒羅列下。 我們再看下該類在模板中的使用,我們先從最基礎的類開始解析
class CEmpytLocalStore{};\ttemplate<class Store = CEmpytLocalStore>\tclass CLocalStoreAccess{\tpublic:\t\ttypedef boost::function< Store& () > func;\t\tStore& GetStore(){return m_pFunc();};\t\tvoid SetStore(func& pCallback){m_pFunc = pCallback;};\tpublic:\t\tfunc m_pFunc;\t};
我們先定義了一個空類——CEmptyLocalStore,它相當於一個預設的“資料庫”。當模板的使用者不需要“資料庫”時,就可以在模板中不宣告“資料庫”類,此時我們的CEmptyLocalStore就生效了。比如我們上例的狀態機可以改成:
class CMachine_Download_Run_App :public AutoStateChart::CAutoStateChartMachine<CMachine_Download_Run_App>
CLocalStoreAccess類主要提供如下作用:
設定訪問“資料庫”類物件的方法——SetStore
獲取“資料庫”類物件——GetStore
成員變數m_pFunc是一個函式指標,用於獲取“資料庫”類物件。該變數將由CLoaclStoreAccess繼承類設定,相當於CLocalStoreAccess暴露了設定訪問“資料庫”類物件的能力。而它並不儲存“資料庫”類物件——它只提供“訪問”能力,而不提供“儲存”能力。
\ttemplate<class Store = CEmpytLocalStore>\tclass CLocalStoreBase:\t\tpublic boost::enable_shared_from_this<CLocalStoreBase<Store>>,\t\tpublic CLocalStoreAccess<Store> {\tpublic:\t\tvoid Init(){ func pfunc = boost::bind(&CLocalStoreBase<Store>::_GetStore, shared_from_this()); SetStore(pfunc);};\tprivate:\t\tStore& _GetStore(){return m_Store;};\tprivate:\t\tStore m_Store;\t};
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類的宣告\ttemplate<class T, class MachineOrCompositeStates, class Store = CEmpytLocalStore>\tclass CAutoStateChartBase:\t\tpublic boost::enable_shared_from_this<CAutoStateChartBase<T,MachineOrCompositeStates,Store>>,\t\tpublic CLocalStoreAccess<Store>\t{\t\tBOOST_TYPEOF_REGISTER_TYPE(T)\tpublic:\t\tstd::string GetCurrentState(){ return typeid(T).name();};\t\tbool IsCompositeStates(){return false;};\t\tvoid SetInitState( const std::string& strState ){}; \tpublic:\t\tvirtual void Entry(){}; \t\tvirtual std::string Exit(){return "";};\t};
該模板類使用第一個模板引數類的類名作為其繼承類的狀態,並使用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>\tclass CAutoStateChartMachine:\t\tpublic boost::enable_shared_from_this<CAutoStateChartMachine<T,LocalStore>>,\t\tpublic CLocalStoreAccess<LocalStore>\t{\tpublic:\t\ttypedef LocalStore SelfStore;\t\ttypedef T Self;\tpublic:\t\tCAutoStateChartMachine(){m_spStore.reset();};\t\tvirtual ~CAutoStateChartMachine(){};\tprivate:\t\tvirtual bool Transition(){return false;};\tpublic:\t\tvoid StartMachine() {\t\t\tif ( !m_spStore ) {\t\t\t\tm_spStore = boost::make_shared<CLocalStoreBase<LocalStore>>();\t\t\t\tm_spStore->Init();\t\t\t\tSetStore( m_spStore->m_pFunc );\t\t\t}\t\t\twhile( Transition()){};\t\t};\tprivate:\t\tvoid Init(){};\tpublic:\t\tbool IsCompositeStates(){return false;};\tprotected:\t\tstd::string m_strCurrentState;\t\tstd::string m_strCondition;\t\tMapString m_MapCompositeStatesSubState;\t\tboost::shared_ptr<CLocalStoreBase<LocalStore>> m_spStore;\t};
我們先看下這個類的成員變數。m_strCurrentState儲存了狀態機在跳轉中的當前狀態,m_strCondition儲存了狀態機中當前狀態之前的狀態的輸出,它用於決定狀態跳轉方向。m_MapCompositeStatesSubState用於儲存狀態機中離開復合狀態時的最後狀態,即它記錄複合狀態機的淺歷史。m_spStore指向“資料庫”類物件。
該模板類最重要的函式就是StartMachine,它在第一次被呼叫時建立了“資料庫”類物件。然後死迴圈呼叫Transition方法。Tansition方法是個虛方法,它是整個模型的核心。狀態機類需要實現自己的Transition方法,以使得狀態機可以運轉起來。
我們再看下複合狀態類的基礎模板
template<class T, class MachineOrCompositeStates, class LocalStore = CEmpytLocalStore>\tclass CCompositeStates:\t\tpublic CAutoStateChartBase<T,MachineOrCompositeStates,LocalStore>{\t\tBOOST_TYPEOF_REGISTER_TYPE(T)\tpublic:\t\tCCompositeStates(){};\t\t~CCompositeStates(){};\tprivate:\t\tvirtual bool Transition(){return false;};\tpublic:\t\tvirtual void Entry(){while(Transition());};\t\tvirtual std::string Exit(){return m_strCondition;};\tpublic:\t\tstd::string GetCurrentState(){return m_strCurrentState;};\t\tbool IsCompositeStates(){return true;};\t\tvoid SetInitState( const std::string& strState ){ m_strCurrentState = strState; };\tprotected:\t\tstd::string m_strCurrentState;\t\tstd::string m_strCondition;\t\tMapString m_MapCompositeStatesSubState;\t};
因為複合狀態也是一種狀態,所以它也要有Entry和Exit兩種方法。而其Entry方法就是呼叫Transition方法。該模板類的Transition方法也是虛方法,這意味著繼承於該模板類的方法也要去實現Transition。
於是所有的重心都集中於Transition方法的實現。
為了讓程式碼美觀,我參考了MFC中使用巨集簡潔程式碼的思路,設計了如下的巨集:
#define STARTSTATE(state)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\tdo {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\tboost::shared_ptr<state> sp = boost::make_shared<state>();\t\t\t\t\\\t\tsp->SetStore( m_pFunc );\t\t\t\t\t\t\t\t\t\t\t\t\\\t\tif ( sp->IsCompositeStates() ) {\t\t\t\t\t\t\t\t\t\t\\\t\t\tstd::string strState = typeid(state).name();\t\t\t\t\t\t\\\t\t\tBOOST_AUTO(it, m_MapCompositeStatesSubState.find(strState));\t\t\\\t\t\tif ( m_MapCompositeStatesSubState.end() != it ) {\t\t\t\t\t\\\t\t\t\tsp->SetInitState(it->second);\t\t\t\t\t\t\t\t\t\\\t\t\t\tif ( DEBUGFRAMEFLAG ) {\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t\t\tstd::string strInitState = it->second;\t\t\t\t\t\t\\\t\t\t\t\tstd::cout<<"CompositeStates SetInitState:"<<strInitState<<std::endl;\\\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\tsp->Entry();\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\tm_strCondition = sp->Exit();\t\t\t\t\t\t\t\t\t\t\t\\\t\tif ( sp->IsCompositeStates() ) {\t\t\t\t\t\t\t\t\t\t\\\t\t\tstd::string strState = typeid(state).name();\t\t\t\t\t\t\\\t\t\tstd::string strInnerState = sp->GetCurrentState();\t\t\t\t\t\\\t\t\tm_MapCompositeStatesSubState[strState] = strInnerState;\t\t\t\t\\\t\t\tif ( DEBUGFRAMEFLAG ) {\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t\tstd::cout<<"CompositeStates SaveState:"<<strState<< " " << strInnerState<< std::endl;\t\\\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t} while (0);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\ #define\tREGISTERSTATECONVERTBEGIN(startstate)\t\t\t\t\t\t\t\t\t\\\tbool Transition() {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\tdo {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\tif ( m_strCurrentState.empty() ) {\t\t\t\t\t\t\t\t\t\\\t\t\t\tm_strCurrentState = typeid(startstate).name();\t\t\t\t\t\\\t\t\t\tSTARTSTATE(startstate);\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t\treturn true;\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t}while(0);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\#define REGISTERSTATECONVERT(fromstate,condition,tostate)\t\t\t\t\t\t\\\t\tdo {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\tstd::string strFromState = typeid(fromstate).name();\t\t\t\t\\\t\t\tstd::string strToState = typeid(tostate).name();\t\t\t\t\t\\\t\t\tif ( DEBUGFRAMEFLAG\t) {\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t\tstd::cout<<"strFromState:"<<strFromState<<std::endl;\t\t\t\t\\\t\t\t\tstd::cout<<"condition:"<<condition<<std::endl;\t\t\t\t\t\t\\\t\t\t\tstd::cout<<"strToState:"<<strToState<<std::endl;\t\t\t\t\t\\\t\t\t\tstd::cout<<"m_strCurrentState:"<<m_strCurrentState<<std::endl;\t\t\\\t\t\t\tstd::cout<<"m_strCondition:"<<m_strCondition<<std::endl<<std::endl;\t\\\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\tif ( IsCompositeStates() ) {\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t\tif ( strFromState != m_strCurrentState\t\t\t\t\t\t\t\t\\\t\t\t\t\t|| ( !m_strCondition.empty() && condition != m_strCondition ) ) { \\\t\t\t\t\t\tbreak;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\telse {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t\tif ( strFromState != m_strCurrentState\t\t\t\t\t\t\t\t\\\t\t\t\t\t|| condition != m_strCondition ) {\t\t\t\t\t\t\t\t\\\t\t\t\t\tbreak;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\tm_strCurrentState = strToState;\t\t\t\t\t\t\t\t\t\t\\\t\t\tSTARTSTATE(tostate);\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t\treturn true;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t\t}while(0);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\#define REGISTERSTATECONVERTEND()\t\t\t\t\t\t\t\t\t\t\t\t\\\t\treturn false;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\t};\t
然後複合狀態類和狀態機類只要使用這些巨集去組織狀態跳轉,就可以清晰的描述過程了。