精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

狀態機編程實例-面向對象的狀態設計模式

碼農愛學習 ? 來源:碼農愛學習 ? 作者:碼農愛學習 ? 2023-06-28 09:04 ? 次閱讀

上篇文章:狀態機編程實例-狀態表法

使用狀態表法,實現了炸彈拆除小游戲的狀態機編程,這也是介紹的狀態機編程的第二種方法。

本篇,繼續介紹狀態機編程的第三種方法:面向對象的設計模式。此方法從名字上看,用到了面向對象的思想,所以本篇的代碼,需要以C++為基礎,利用C++中“類”的特性,實現狀態機中狀態的管理。

1 面向對象的狀態設計模式

面向對象的狀態設計模式,其核心思想在于:它是通過不同的類來表示不同的狀態,當狀態機從一個狀態轉換到另一個狀態時,它表現為在運行時改變自己的類。

回顧第一篇時繪制的炸彈拆除小游戲的狀態圖,有2個狀態和4個事件:

使用面向對象的狀態設計模式,此例子中的****兩個工作狀態,就要設計為兩個類,如下圖中的設置狀態(SettingState)和倒計時狀態(TimingState)。

先簡單說明一下下面這個圖,此圖屬于UML類圖,相關介紹可參考: UML簡介與類圖詳解

Bomb3與BombState是組合關系,BombState是一個抽象類,SettingState與TimingState繼承自BombState,屬于繼承關系

可以注意到,此模式引入了一個炸彈狀態的****抽象基類BombState,用于派生具體的工作狀態類。

該抽象類為炸彈的兩個工作狀態聲明了一些公共的接口:onUP、onDOWN、onARM和onTICk,這些接口對應于此例子中的四個事件

兩個工作狀態類:SettingState類和TimingState類,通過定義自己的onUP等操作,實現各自狀態類需要處理的功能。

這種設計模式下:

  • 如果需要增加新的事件,則需要給抽象類BombState增加新的操作
  • 如果需要增加新的狀態,則需要給抽象類BombState增加新的子類

此模式還設計了一個上下文類Bomb3,它通過一個抽象類BombState的指針來實現炸彈狀態的維護。

什么是上下文?

編程中提到的上下文(context),可以理解為環境或語境,每一段程序都有很多的外部變量,一旦寫的一段程序中有了外部變量,這段程序就是不完整的,不能獨立運行,要想讓他運行,就必須把所有的外部變量的值一個一個的全部傳進去,這些值的集合就叫作上下文。

本例中,BombState的運行,就需要一個上下文類作為其參數,這個參數就是Bomb3類。

此外,它還包含需要用到的****擴展狀態變量

  • timeout(超時時間)
  • code(用戶輸入的拆除密碼)
  • defuse(默認的拆除密碼)

并通過提供對BombState一樣的接口,即每派生一個事件對應一個操作。

在上下文類Bomb3中的事件處理,是通過state_指針實現的,它代表了對當前狀態對象的全部特定請求,狀態的改變對應于當前工作狀態類對象的改變,通過上下文操作tran()實現。

2 實現

介紹了面向對象的狀態設計模式后,下面來看下如何使用C++語言進行對應的代碼實現。

2.1 類的結構

首先來看下要實現的幾個類的結構定義。

2.1.1 狀態基類與派生類

下面是炸彈狀態基類(BombState)的結構,以及派生的兩個具體狀態類(SettingState和TimingState)的結構。

class Bomb3; //事先聲明炸彈業務類
?
//炸彈狀態基類
class BombState
{
  public:
    virtual void onUP(Bomb3 *) const {}
    virtual void onDOWN(Bomb3 *) const {}
    virtual void onARM(Bomb3 *) const {}
    virtual void onTICK(Bomb3 *, uint8_t) const {}
};
?
//設置狀態-類,繼承于炸彈狀態基類
class SettingState : public BombState
{
  public:
    virtual void onUP(Bomb3 *context) const;
    virtual void onDOWN(Bomb3 *context) const;
    virtual void onARM(Bomb3 *context) const;
};
?
//倒計時狀態-類,繼承于炸彈狀態基類
class TimingState : public BombState
{
  public:
    virtual void onUP(Bomb3 *context) const;
    virtual void onDOWN(Bomb3 *context) const;
    virtual void onARM(Bomb3 *context) const;
    virtual void onTICK(Bomb3 *context, uint8_t fine_time) const;
};

注意這里用到了C++虛函數的特性。

虛函數,是指被virtual關鍵字修飾的成員函數。

虛函數的作用:

  • 實現動態聯編,在函數運行階段動態的選擇合適的成員函數
  • 實現多態性(Polymorphism),多態性是將接口與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異,而采用不同的策略。

虛函數主要通過V-Table虛函數表來實現,該表主要包含一個類的虛函數的地址表,可解決繼承、覆蓋的問題。當我們使用一個父類的指針去操作一個子類時,虛函數表就像一個地圖一樣,可指明實際所應該調用的函數。

此外,對事件的處理,用到了指向類對象的指針(Bomb3 *context

指針也就是內存地址,指針變量是用來存放內存地址的變量,不同類型的指針變量所占用的存儲單元長度是相同的,而存放數據的變量因數據的類型不同,所占用的存儲空間長度也不同。

有了指針以后,不僅可以對數據本身,也可以對存儲數據的變量地址進行操作。

創建對像時,編譯系統會為每一個對像分配一定的存儲空間,以存放其成員,對象空間的起始地址就是對象的指針。可以定義一個指針變量,用來存和對象的指針。

2.1.2 炸彈業務類

炸彈業務類,也就是上面提到的上下文類。

class Bomb3
{
  public:
    Bomb3(uint8_t defuse) : m_defuse(defuse) {}
?
    void init(); //狀態機初始化接口
?
    //處理各種事件
    void onUP();
    void onDOWN();
    void onARM();
    void onTICK(uint8_t fine_time);
?
  private:
    //進行狀態轉換
    void tran(BombState const *target);
?
  private:
    BombState const *m_pState; //[狀態變量]
    uint8_t m_timeout; // 爆炸前的秒數
    uint8_t m_code;    // 當前輸入的解除炸彈的密碼
    uint8_t m_defuse;  // 解除炸彈的拆除密碼
    uint8_t m_errcnt;  // 當前拆除失敗的次數
?
  private:
    SettingState const m_settingState; //[設置狀態]
    TimingState const m_timingState; //[倒計時狀態]
?
    friend class SettingState;
    friend class TimingState;
};

注意這里又用到了C++的友元特性。

友元包括友元函數與友元類,這里先介紹下本例使用到的友元類

友元類的作用:如果把在A類(如本例中的上下文類Bomb3)中聲明了友元類B(如本例中的SettingState和TimingState),那么A類的所有成員函數,可以被B類的所以成員函數訪問。

友元使用前提:某個類需要實現某種功能,但是這個類自身,因為各種原因,無法自己實現,需要借助于“外力”才能實現。

本例中,SettingState和TimingState,需要借助上下文類Bomb3,實現狀態轉換等功能

2.2 類的具體實現

2.2.1 狀態基類與派生類

體會友元類的用法:Bomb3中聲明了SettingState是友元,SettingState則可以訪問Bomb3的成員變量(如m_timeout變量)和成員函數(如tran函數)。

體會上下文類Bomb3的作用:設置狀態SettingState和倒計時狀態TimingState,都是操作Bomb3這個上下文類,實現對應狀態下的業務功能。

//---------------設置狀態-類,具體實現---------------
void SettingState::onUP(Bomb3 *context) const
{
  if (context- >m_timeout < 60)
  {
    ++context- >m_timeout;
    bsp_display_set_time(context- >m_timeout);
  }
}
void SettingState::onDOWN(Bomb3 *context) const
{
  if (context- >m_timeout > 1)
  {
    --context- >m_timeout;
    bsp_display_set_time(context- >m_timeout);
  }
}
void SettingState::onARM(Bomb3 *context) const
{
  context- >m_code = 0;
  context- >tran(&context- >m_timingState); //[轉換到倒計時狀態]
}
?
//---------------倒計時狀態-類,具體實現---------------
void TimingState::onUP(Bomb3 *context) const
{
  context- >m_code < <= 1;
  context- >m_code |= 1;
  bsp_display_user_code(context- >m_code);
}
void TimingState::onDOWN(Bomb3 *context) const
{
  context- >m_code < <= 1;
  bsp_display_user_code(context- >m_code);
}
void TimingState::onARM(Bomb3 *context) const
{
  if (context- >m_code == context- >m_defuse)
  {
    context- >tran(&context- >m_settingState); //[轉換到設置狀態]
    bsp_display_user_success(); //炸彈拆除成功
    context- >init();
  }
  else
  {
    context- >m_code = 0;
    bsp_display_user_code(context- >m_code);
    bsp_display_user_err(++context- >m_errcnt);
  }
}
void TimingState::onTICK(Bomb3 *context, uint8_t fine_time) const
{
  if (fine_time == 0)
  {
    --context- >m_timeout;
    bsp_display_remain_time(context- >m_timeout);
    if (context- >m_timeout == 0)
    {
      bsp_display_bomb(); //顯示爆炸效果
      context- >init();
    }
  }
}

2.2.2 炸彈業務類

炸彈業務類,提供通用的事件處理接口:onUP、onDOWN、onARM和onTICK,其內部具體如果處理,是由m_pState指向的具體狀態類決定的,狀態指針m_pState的改變,是通過tran函數實現的,tran在初始轉換和具體的狀態類的成員函數中被調用。

//初始化
void Bomb3::init()
{
  m_timeout = INIT_TIMEOUT;
  m_errcnt  = 0;
  tran(&m_settingState); //[初始轉換]
}
//處理各種事件
void Bomb3::onUP()
{
  m_pState- >onUP(this);
}
void Bomb3::onDOWN()
{
  m_pState- >onDOWN(this);
}
void Bomb3::onARM()
{
  m_pState- >onARM(this);
}
void Bomb3::onTICK(uint8_t fine_time)
{
  m_pState- >onTICK(this, fine_time);
}
//進行狀態轉換
void Bomb3::tran(BombState const *target) 
{
  m_pState = target;
}

2.3 主函數

使用面向對象的狀態設計模式,炸彈拆除小游戲的主函數會比較簡潔:

  • 首先實例化一個Bomb3上下文類的實例bomb
  • 然后進行bomb的初始化(狀態轉換)
  • 最后在狀態機循環中,根據不同的按鍵或TICK事件,調用bomb對應的事件處理接口

體會,本例的事件處理,調用的是通用的bomb事件處理接口,其內部會根據當前的具體狀態,調用對應狀態類的事件處理函數。

static Bomb3 bomb(0x0D); // 構造, 密碼1101
?
void setup(void)
{
  //省略...
  bomb.init(); // 初始轉化
}
?
void loop(void)
{
  static int fine_time = 0;
  delay(100);
?
  if (++fine_time == 10)
  {
    fine_time = 0;
  }
?
  char tmp_buffer[256];
  sprintf(tmp_buffer, "T(%1d)%c", fine_time, (fine_time == 0) ? '\\n' : ' ');
  Serial.print(tmp_buffer);
?
  bomb.onTICK(fine_time); //處理Tick事件
?
  BombSignals userSignal = bsp_key_check_signal();
  if (userSignal != SIG_MAX)
  {
    switch (userSignal)
    {
      case UP_SIG: //UP鍵事件
      {
        Serial.print("\\nUP  : ");
        bomb.onUP();
        break;
      }
      case DOWN_SIG: //DOWN鍵事件
      {
        Serial.print("\\nDOWN: ");
        bomb.onDOWN();
        break;
      }
      case ARM_SIG: //ARM鍵事件
      {
        Serial.print("\\nARM : ");
        bomb.onARM();
        break;
      }
      default:break;
    }
  }
}

3 總結

本編介紹了狀態機編程的第3種方法——面向對象的狀態設計模式,通過C++的繼承特性,以及類指針,實現炸彈拆除小游戲中的狀態機功能。

本篇,需要重點體會的點包括:

  • 狀態基類與派生類的關系
  • 虛函數與友元類的作用
  • 上下文類的使用
  • 指向對象的指針的使用
    審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 嵌入式
    +關注

    關注

    5071

    文章

    19026

    瀏覽量

    303492
  • 編程
    +關注

    關注

    88

    文章

    3596

    瀏覽量

    93609
  • 狀態機
    +關注

    關注

    2

    文章

    492

    瀏覽量

    27486
收藏 人收藏

    評論

    相關推薦

    STM32狀態機編程實例——全自動洗衣(下)

    本篇在上篇全自動洗衣狀態機編程實例的基礎上,增加了OLED來更新直觀的展示洗衣的工作狀態
    的頭像 發表于 09-07 08:47 ?3257次閱讀
    STM32<b class='flag-5'>狀態機</b><b class='flag-5'>編程</b><b class='flag-5'>實例</b>——全自動洗衣<b class='flag-5'>機</b>(下)

    狀態機編程實例-狀態表法

    上篇文章,使用嵌套switch-case法的狀態機編程,實現了一個炸彈拆除小游戲。本篇,繼續介紹狀態機編程的第二種方法:狀態表法,來實現炸彈
    的頭像 發表于 06-20 09:05 ?1978次閱讀
    <b class='flag-5'>狀態機</b><b class='flag-5'>編程</b><b class='flag-5'>實例</b>-<b class='flag-5'>狀態</b>表法

    STM32按鍵消抖——入門狀態機思維

    本篇介紹了嵌入式軟件開發中常用的狀態機編程實現,并通過按鍵消抖實例,以常用的switch-case形式,實現了對應的狀態機編程代碼實現,并通
    的頭像 發表于 09-02 21:54 ?4776次閱讀
    STM32按鍵消抖——入門<b class='flag-5'>狀態機</b>思維

    狀態機編程實例-嵌套switch-case法

    嵌入式軟件開發中,狀態機編程是一個比較實用的代碼實現方式,特別適用于事件驅動的系統。本篇,以一個炸彈拆除的小游戲為例,介紹狀態機編程的思路。
    的頭像 發表于 06-15 09:01 ?1718次閱讀
    <b class='flag-5'>狀態機</b><b class='flag-5'>編程</b><b class='flag-5'>實例</b>-嵌套switch-case法

    Spring狀態機的實現原理和使用方法

    說起 Spring 狀態機,大家很容易聯想到這個狀態機和設計模式狀態模式的區別是啥呢?沒錯,Spring
    的頭像 發表于 12-26 09:39 ?1879次閱讀
    Spring<b class='flag-5'>狀態機</b>的實現原理和使用方法

    Verilog狀態機+設計實例

    在verilog中狀態機的一種很常用的邏輯結構,學習和理解狀態機的運行規律能夠幫助我們更好地書寫代碼,同時作為一種思想方法,在別的代碼設計中也會有所幫助。 一、簡介 在使用過程中我們常說
    的頭像 發表于 02-12 19:07 ?3856次閱讀
    Verilog<b class='flag-5'>狀態機</b>+設計<b class='flag-5'>實例</b>

    玩轉Spring狀態機

    說起Spring狀態機,大家很容易聯想到這個狀態機和設計模式狀態模式的區別是啥呢?沒錯,Spring
    的頭像 發表于 06-25 14:21 ?889次閱讀
    玩轉Spring<b class='flag-5'>狀態機</b>

    raw os 之狀態機編程

    狀態機編程的歷史很可能久于傳統的操作系統, 傳統的一個大while 循環模式普遍用到了狀態機模式編程
    發表于 02-27 14:35

    什么是狀態機狀態機是如何編程的?

    什么是狀態機狀態機是如何編程的?
    發表于 10-20 07:43

    狀態機實例(VHDL源代碼)

    狀態機實例(VHDL源代碼):
    發表于 05-27 10:27 ?59次下載
    <b class='flag-5'>狀態機</b><b class='flag-5'>實例</b>(VHDL源代碼)

    狀態機原理及用法

    狀態機原理及用法狀態機原理及用法狀態機原理及用法
    發表于 03-15 15:25 ?0次下載

    狀態機概述 如何理解狀態機

    本篇文章包括狀態機的基本概述以及通過簡單的實例理解狀態機
    的頭像 發表于 01-02 18:03 ?1w次閱讀
    <b class='flag-5'>狀態機</b>概述  如何理解<b class='flag-5'>狀態機</b>

    什么是狀態機狀態機5要素

    玩單片還可以,各個外設也都會驅動,但是如果讓你完整的寫一套代碼時,卻無邏輯與框架可言。這說明編程還處于比較低的水平,你需要學會一種好的編程框架或者一種編程思想!比如模塊化
    的頭像 發表于 07-27 11:23 ?2w次閱讀
    什么是<b class='flag-5'>狀態機</b>?<b class='flag-5'>狀態機</b>5要素

    狀態模式(狀態機)

    以前寫狀態機,比較常用的方式是用 if-else 或 switch-case,高級的一點是函數指針列表。最近,看了一文章《c語言設計模式狀態模式(
    發表于 12-16 16:53 ?9次下載
    <b class='flag-5'>狀態</b><b class='flag-5'>模式</b>(<b class='flag-5'>狀態機</b>)

    如何以面向對象的思想設計有限狀態機

    有限狀態機又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學計算模型,用英文縮寫也被簡...
    發表于 02-07 11:23 ?4次下載
    如何以<b class='flag-5'>面向</b><b class='flag-5'>對象</b>的思想設計有限<b class='flag-5'>狀態機</b>