程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 使用C++實現一套簡單的狀態機模型——實例

使用C++實現一套簡單的狀態機模型——實例

編輯:C++入門知識

使用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地址下載運行成功則認為執行成功 對於已安裝該軟件的情況: 運行失敗則先進行卸載,然後進入“未安裝該軟件”邏輯運行成功則認為執行成功

我們以狀態圖來表示:

\

圖中“下載復合狀態”是一個具有淺歷史特性的復合狀態;“安裝後運行狀態”是一個狀態組合集,它讓一組復雜的狀態轉換關系縮變成一種狀態。這樣如果其他地方需要復用該組合時,只要引入該組合狀態名即可。<喎?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
{
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
{
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
{
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
{
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 spc = boost::make_shared();
    spc->StartMachine();

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved