RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

狀態(tài)機(jī)編程實例-面向?qū)ο蟮臓顟B(tài)設(shè)計模式

碼農(nóng)愛學(xué)習(xí) ? 來源:碼農(nóng)愛學(xué)習(xí) ? 作者:碼農(nóng)愛學(xué)習(xí) ? 2023-06-28 09:04 ? 次閱讀

上篇文章:狀態(tài)機(jī)編程實例-狀態(tài)表法

使用狀態(tài)表法,實現(xiàn)了炸彈拆除小游戲的狀態(tài)機(jī)編程,這也是介紹的狀態(tài)機(jī)編程的第二種方法。

本篇,繼續(xù)介紹狀態(tài)機(jī)編程的第三種方法:面向?qū)ο蟮脑O(shè)計模式。此方法從名字上看,用到了面向?qū)ο蟮乃枷?,所以本篇的代碼,需要以C++為基礎(chǔ),利用C++中“類”的特性,實現(xiàn)狀態(tài)機(jī)中狀態(tài)的管理。

1 面向?qū)ο蟮臓顟B(tài)設(shè)計模式

面向?qū)ο蟮臓顟B(tài)設(shè)計模式,其核心思想在于:它是通過不同的類來表示不同的狀態(tài),當(dāng)狀態(tài)機(jī)從一個狀態(tài)轉(zhuǎn)換到另一個狀態(tài)時,它表現(xiàn)為在運行時改變自己的類。

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

使用面向?qū)ο蟮臓顟B(tài)設(shè)計模式,此例子中的****兩個工作狀態(tài),就要設(shè)計為兩個類,如下圖中的設(shè)置狀態(tài)(SettingState)和倒計時狀態(tài)(TimingState)。

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

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

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

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

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

這種設(shè)計模式下:

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

此模式還設(shè)計了一個上下文類Bomb3,它通過一個抽象類BombState的指針來實現(xiàn)炸彈狀態(tài)的維護(hù)。

什么是上下文?

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

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

此外,它還包含需要用到的****擴(kuò)展?fàn)顟B(tài)變量

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

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

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

2 實現(xiàn)

介紹了面向?qū)ο蟮臓顟B(tài)設(shè)計模式后,下面來看下如何使用C++語言進(jìn)行對應(yīng)的代碼實現(xiàn)。

2.1 類的結(jié)構(gòu)

首先來看下要實現(xiàn)的幾個類的結(jié)構(gòu)定義。

2.1.1 狀態(tài)基類與派生類

下面是炸彈狀態(tài)基類(BombState)的結(jié)構(gòu),以及派生的兩個具體狀態(tài)類(SettingState和TimingState)的結(jié)構(gòu)。

class Bomb3; //事先聲明炸彈業(yè)務(wù)類
?
//炸彈狀態(tài)基類
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 {}
};
?
//設(shè)置狀態(tài)-類,繼承于炸彈狀態(tài)基類
class SettingState : public BombState
{
  public:
    virtual void onUP(Bomb3 *context) const;
    virtual void onDOWN(Bomb3 *context) const;
    virtual void onARM(Bomb3 *context) const;
};
?
//倒計時狀態(tài)-類,繼承于炸彈狀態(tài)基類
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++虛函數(shù)的特性。

虛函數(shù),是指被virtual關(guān)鍵字修飾的成員函數(shù)。

虛函數(shù)的作用:

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

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

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

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

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

創(chuàng)建對像時,編譯系統(tǒng)會為每一個對像分配一定的存儲空間,以存放其成員,對象空間的起始地址就是對象的指針??梢远x一個指針變量,用來存和對象的指針。

2.1.2 炸彈業(yè)務(wù)類

炸彈業(yè)務(wù)類,也就是上面提到的上下文類。

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

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

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

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

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

本例中,SettingState和TimingState,需要借助上下文類Bomb3,實現(xiàn)狀態(tài)轉(zhuǎn)換等功能

2.2 類的具體實現(xiàn)

2.2.1 狀態(tài)基類與派生類

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

體會上下文類Bomb3的作用:設(shè)置狀態(tài)SettingState和倒計時狀態(tài)TimingState,都是操作Bomb3這個上下文類,實現(xiàn)對應(yīng)狀態(tài)下的業(yè)務(wù)功能。

//---------------設(shè)置狀態(tài)-類,具體實現(xiàn)---------------
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); //[轉(zhuǎn)換到倒計時狀態(tài)]
}
?
//---------------倒計時狀態(tài)-類,具體實現(xiàn)---------------
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); //[轉(zhuǎn)換到設(shè)置狀態(tài)]
    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 炸彈業(yè)務(wù)類

炸彈業(yè)務(wù)類,提供通用的事件處理接口:onUP、onDOWN、onARM和onTICK,其內(nèi)部具體如果處理,是由m_pState指向的具體狀態(tài)類決定的,狀態(tài)指針m_pState的改變,是通過tran函數(shù)實現(xiàn)的,tran在初始轉(zhuǎn)換和具體的狀態(tài)類的成員函數(shù)中被調(diào)用。

//初始化
void Bomb3::init()
{
  m_timeout = INIT_TIMEOUT;
  m_errcnt  = 0;
  tran(&m_settingState); //[初始轉(zhuǎn)換]
}
//處理各種事件
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);
}
//進(jìn)行狀態(tài)轉(zhuǎn)換
void Bomb3::tran(BombState const *target) 
{
  m_pState = target;
}

2.3 主函數(shù)

使用面向?qū)ο蟮臓顟B(tài)設(shè)計模式,炸彈拆除小游戲的主函數(shù)會比較簡潔:

  • 首先實例化一個Bomb3上下文類的實例bomb
  • 然后進(jìn)行bomb的初始化(狀態(tài)轉(zhuǎn)換)
  • 最后在狀態(tài)機(jī)循環(huán)中,根據(jù)不同的按鍵或TICK事件,調(diào)用bomb對應(yīng)的事件處理接口

體會,本例的事件處理,調(diào)用的是通用的bomb事件處理接口,其內(nèi)部會根據(jù)當(dāng)前的具體狀態(tài),調(diào)用對應(yīng)狀態(tài)類的事件處理函數(shù)。

static Bomb3 bomb(0x0D); // 構(gòu)造, 密碼1101
?
void setup(void)
{
  //省略...
  bomb.init(); // 初始轉(zhuǎn)化
}
?
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 總結(jié)

本編介紹了狀態(tài)機(jī)編程的第3種方法——面向?qū)ο蟮臓顟B(tài)設(shè)計模式,通過C++的繼承特性,以及類指針,實現(xiàn)炸彈拆除小游戲中的狀態(tài)機(jī)功能。

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

  • 狀態(tài)基類與派生類的關(guān)系
  • 虛函數(shù)與友元類的作用
  • 上下文類的使用
  • 指向?qū)ο蟮闹羔樀氖褂?/strong>
    審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 嵌入式
    +關(guān)注

    關(guān)注

    5082

    文章

    19104

    瀏覽量

    304797
  • 編程
    +關(guān)注

    關(guān)注

    88

    文章

    3614

    瀏覽量

    93685
  • 狀態(tài)機(jī)
    +關(guān)注

    關(guān)注

    2

    文章

    492

    瀏覽量

    27528
收藏 人收藏

    評論

    相關(guān)推薦

    STM32狀態(tài)機(jī)編程實例——全自動洗衣機(jī)(下)

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

    狀態(tài)機(jī)編程實例-狀態(tài)表法

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

    STM32按鍵消抖——入門狀態(tài)機(jī)思維

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

    狀態(tài)機(jī)編程實例-嵌套switch-case法

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

    Spring狀態(tài)機(jī)的實現(xiàn)原理和使用方法

    說起 Spring 狀態(tài)機(jī),大家很容易聯(lián)想到這個狀態(tài)機(jī)和設(shè)計模式狀態(tài)模式的區(qū)別是啥呢?沒錯,Spring
    的頭像 發(fā)表于 12-26 09:39 ?1977次閱讀
    Spring<b class='flag-5'>狀態(tài)機(jī)</b>的實現(xiàn)原理和使用方法

    Verilog狀態(tài)機(jī)+設(shè)計實例

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

    玩轉(zhuǎn)Spring狀態(tài)機(jī)

    說起Spring狀態(tài)機(jī),大家很容易聯(lián)想到這個狀態(tài)機(jī)和設(shè)計模式狀態(tài)模式的區(qū)別是啥呢?沒錯,Spring
    的頭像 發(fā)表于 06-25 14:21 ?931次閱讀
    玩轉(zhuǎn)Spring<b class='flag-5'>狀態(tài)機(jī)</b>

    raw os 之狀態(tài)機(jī)編程

    狀態(tài)機(jī)編程的歷史很可能久于傳統(tǒng)的操作系統(tǒng), 傳統(tǒng)的一個大while 循環(huán)模式普遍用到了狀態(tài)機(jī)模式編程
    發(fā)表于 02-27 14:35

    什么是狀態(tài)機(jī)狀態(tài)機(jī)是如何編程的?

    什么是狀態(tài)機(jī)?狀態(tài)機(jī)是如何編程的?
    發(fā)表于 10-20 07:43

    狀態(tài)機(jī)實例(VHDL源代碼)

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

    狀態(tài)機(jī)原理及用法

    狀態(tài)機(jī)原理及用法狀態(tài)機(jī)原理及用法狀態(tài)機(jī)原理及用法
    發(fā)表于 03-15 15:25 ?0次下載

    狀態(tài)機(jī)概述 如何理解狀態(tài)機(jī)

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

    什么是狀態(tài)機(jī)?狀態(tài)機(jī)5要素

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

    狀態(tài)模式(狀態(tài)機(jī))

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

    如何以面向對象的思想設(shè)計有限狀態(tài)機(jī)

    有限狀態(tài)機(jī)又稱有限狀態(tài)自動機(jī),簡稱狀態(tài)機(jī),是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學(xué)計算模型,用英文縮寫也被簡...
    發(fā)表于 02-07 11:23 ?4次下載
    如何以<b class='flag-5'>面向</b><b class='flag-5'>對象</b>的思想設(shè)計有限<b class='flag-5'>狀態(tài)機(jī)</b>
    RM新时代网站-首页