一般來說,“狀態機”是一種表達狀態轉換變換邏輯的方法。曾經有人和我討論過為什麼不直接用ifelse,而要使用“狀態機”去實現一些邏輯,認為使用“狀態機”是一種炫技的表現。然而對於大型復雜邏輯的變化和跳轉,使用ifelse將帶來代碼難以閱讀等弊端。其實ifelse也是一種狀態機實現的方式。
之前我們有個業務和操作系統有著強烈的關聯,而我們希望比較清晰地描述整個業務中各個子業務的過程,就引入了狀態機描述的方式。可是當時的狀態機是使用if else方法描述,顯得整個過程比較臃腫,閱讀起來也不夠清晰。於是我嘗試引入第三方的狀態機庫來重構這塊的業務——比如boost裡的狀態機庫。可是使用過程中感覺到了很多不便,索性自己動手實現一套清晰優雅的狀態機模型。(轉載請指明出於breaksoftware的csdn博客)
編寫模型之前,我們需要了解什麼是狀態機。我在搜索引擎上搜索到了若干結果,但是大部分都顯得非常學術化。而實現一個大而全、包羅萬象、放之四海而皆適宜的狀態機模型也並非我的設計初衷。我設計的狀態機具有如下特性:單線程、淺歷史。單線程即我們的狀態機是在一個線程內部運行的,不受外界其他線程干擾,這樣我們在設計時就不用考慮多線程編程的問題。淺歷史是狀態機中的一個概念,它是指只記錄最高一層復合狀態的最後離開狀態。這個特性如果有不了解的,可以先去搜索下。在實踐中,該特性還是非常有用的。
我們以一個簡單、可能不恰當的例子來引入我這個狀態機。我們先設計一個應用場景:給用戶電腦安裝軟件並運行。這個場景我們可以拆分為如下幾個邏輯:
檢測是否安裝下載安裝包解壓安裝包並安裝運行這四個邏輯並不復雜,我們將其定義為基礎狀態——一種可以持續一段時間且內部執行邏輯我們不關心的狀態。為了讓這個邏輯變得稍微有點復雜,我們設計如下要求:
對於未安裝該軟件的情況:
從A地址下載安裝包失敗後從B地址下載從B地址下載安裝包失敗後從C地址下載從C地址下載安裝包失敗後認為執行失敗下載成功後,檢測CPU是否繁忙我們以狀態圖來表示:
圖中“下載復合狀態”是一個具有淺歷史特性的復合狀態;“安裝後運行狀態”是一個狀態組合集,它讓一組復雜的狀態轉換關系縮變成一種狀態。這樣如果其他地方需要復用該組合時,只要引入該組合狀態名即可。<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+ICAgICAgICDO0sPHtNO4w8Sj0M3KudPD1d+1xL3HtsjIpb+0yOe6zsilyei8xrrNseDQtLT6wuujrNbB09q0+sLr1tC1xMSjsOW6zbqvyv2/ydLUz8i69sLUtfSjrM7Sw8fPyMHLveLG5LTzuMXKudPDoaM8L3A+CjxwPiAgICAgICAgtNPJz8281tDO0sPHv8nS1Mi3tqjT0Mjnz8LK5LP2zPW8/jwvcD4KPHA+PC9wPgo8cHJlIGNsYXNzPQ=="brush:java;">/* 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可以發現,該類非常簡單。我們只要用模板申明好類(模板參數:自己、狀態機類、存儲類),並實現Entry和Exit兩個函數就行了。我們再看下下載的復合狀態類{ public: CSimpleState_Download_From_A(void) {}; ~CSimpleState_Download_From_A(void){}; public: void Entry() { }; std::string Exit() { return CONDITION_DOWNLOAD_SUC; }; };
#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這個類也非常簡單,它對應於圖中的{ 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該類的寫法也很簡單REGISTERSTATECONVERTBEGIN、REGISTERSTATECONVERT和REGISTERSTATECONVERTEND三個宏構成了整個狀態跳轉圖{ 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() };
最後我們再看下狀態機的類
#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它也是通過三個宏構成了整個邏輯跳轉。{ 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_ptrspc = boost::make_shared (); spc->StartMachine();