上篇文章:狀態(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();
}
}
}