當前位置:首頁 > 公眾號精選 > 嵌入式大雜燴
[導讀]來源:embed linux?share 作者:亞索老哥 模式動機 狀態(tài)模式(狀態(tài)機)是嵌入式開發(fā)中最重要、最核心的設計模式之一,毫不夸張的說,是否熟練掌握狀態(tài)模式,很大程度上直接決定了嵌入式工程師的代碼掌控能力。在嵌入式開發(fā)里面,幾乎80%以上的程序都有狀態(tài)模式(

來源:embed linux share

作者:亞索老哥


模式動機

狀態(tài)模式(狀態(tài)機)是嵌入式開發(fā)中最重要、最核心的設計模式之一,毫不夸張的說,是否熟練掌握狀態(tài)模式,很大程度上直接決定了嵌入式工程師的代碼掌控能力。在嵌入式開發(fā)里面,幾乎80%以上的程序都有狀態(tài)模式(狀態(tài)機)的影子。在一個思路清晰而且高效的程序中,必然有狀態(tài)模式(狀態(tài)機)身影浮現(xiàn)。但是很多嵌入式開發(fā)者只是掌握一些很基礎的狀態(tài)機編程,對狀態(tài)機編程如果提高程序的可維護性和可拓展性并沒有一個深刻的理解。

這里我通過一個簡單易懂的MP3播放器案例,把自己獨家總結的狀態(tài)機六步法分享給大家,幫助大家在啃下狀態(tài)機這塊硬骨頭。相信你深度掌握狀態(tài)機編程以后,你優(yōu)雅美觀的代碼會讓同事朋友們眼前一亮,嘖嘖稱贊。

生活中的狀態(tài)模式(狀態(tài)機)

幾乎在所有的復雜項目里面,都充斥著各種事物狀態(tài)的變化。這是因為我們身處的物理世界本來就是一個動態(tài)多變的環(huán)境,自然我們開發(fā)的程序也要根據(jù)事物不同時刻不同場景的狀態(tài),不斷調整自身的行為屬性。

比如電影《分裂》里面,詹姆斯·麥卡沃伊飾演的男主Kevin患有精神分裂,有著多重人格疾病,他被精神病醫(yī)生Dr. Fletcher診斷出有23重人格,可以隨時間或境遇切換,一會變成精明聰穎的律師,一會是懦弱的失敗者總是要自殺,一個境遇觸發(fā)又是憤怒的殺人暴徒,這人格切換速度,喪心病狂到令人發(fā)指。

想象一下,假如我們要在程序中實現(xiàn)這樣一個角色,就必須要有一個良好的狀態(tài)變化設計,才能保證主人公在快速切換狀態(tài)的情況下,都能擁有與之匹配的精神狀態(tài)和行為舉止。

場景案例

場景:設計一個簡單的MP3播放器,要求兩個按鍵(播放/暫停、停止)分別控制MP3的播放/停止功能。

如下表所示:

按鍵 功能
[play/pause] 播放/暫停
[stop] 停止

狀態(tài)遷移圖

在狀態(tài)模式的設計開發(fā)中,我們通常借助狀態(tài)遷移圖來進行多個狀態(tài)的分析。本案例中的MP3播放器,狀態(tài)遷移圖如下圖所示:

雖然圖示很簡單,但是非常有用,因為各按鍵按下后,MP3播放器的狀態(tài)變化一目了然,根據(jù)狀態(tài)遷移圖,我們就可以著手程序的編寫了。

我們先來看一個狀態(tài)模式(狀態(tài)機)的入門級別的實現(xiàn)--簡單狀態(tài)機。其實就是通過大量的switch/case和if/else,在很多項目中經常可以看到類似的代碼:

#include <stdio.h>

void stopPlayer();
void pausePlayer();
void resumePlayer();
void startPlayer();
//按鍵的動作類型
typedef enum {
EV_STOP,
EV_PLAY_PAUSE
}EventCode;

//MP3的狀態(tài)
enum{
ST_IDLE,
ST_PLAY,
ST_PAUSE
};

//MP3當前狀態(tài)
char state;

//MP3狀態(tài)初始化
void init()
{
state = ST_IDLE;
}

//狀態(tài)機處理MP3的過程變化
void onEvent(EventCode ec)
{
switch (state)
{
case ST_IDLE:
if(EV_PLAY_PAUSE == ec)
startPlayer();
break;
case ST_PLAY:
if(EV_STOP == ec)
stopPlayer();
else if(EV_PLAY_PAUSE == ec)
pausePlayer();
break;
case ST_PAUSE:
if(EV_STOP == ec)
stopPlayer();
else if(EV_PLAY_PAUSE == ec)
resumePlayer();
break;
default:
break;
}
}

void stopPlayer()
{
state = ST_IDLE;
printf("停止播放音樂\n");
}

void pausePlayer()
{
state = ST_PAUSE;
printf("暫停播放音樂\n");
}

void resumePlayer()
{
state = ST_PLAY;
printf("恢復播放音樂\n");
}

void startPlayer()
{
state = ST_PLAY;
printf("開始播放音樂\n");
}
//主程序實現(xiàn)MP3的播放控制
void main()
{
init();
onEvent(EV_PLAY_PAUSE);//播放
onEvent(EV_PLAY_PAUSE);//暫停
onEvent(EV_PLAY_PAUSE);//繼續(xù)播放
onEvent(EV_STOP); //停止
}

代碼已經在c在線工具|菜鳥工具中運行驗證,讀者也可以自行驗證。運行結果如下:

開始播放音樂
暫停播放音樂
恢復播放音樂
停止播放音樂

在上面的代碼實現(xiàn)中,主要是在onEvent函數(shù)中,以MP3的當前狀態(tài)作為判斷條件進行相應的分支改動,簡單地按照狀態(tài)遷移圖,實現(xiàn)了功能。

但是我們觀察onEvent函數(shù),不難發(fā)現(xiàn)其中有大量的swith...case這樣的判斷(if...else也是一樣).對于MP3播放器這樣簡單的例子,這樣的代碼還是不難閱讀和維護的。但是當狀態(tài)和事件增加后,onEvent函數(shù)就會變得非常龐大,這是因為該函數(shù)的代碼行數(shù)與狀態(tài)和事件數(shù)量的乘積成正比,直接導致代碼行數(shù)爆炸增長,代碼會越發(fā)變得難以閱讀和維護。

其次,程序的擴展性非常差,無論是我們新增一種狀態(tài),還是新增一種按鍵動作,onEvent函數(shù)都要大改特改,極難保障程序的穩(wěn)定性。

解決方案

核心思路:我們可以利用C語言的多態(tài)特性來分解復雜的條件分支(關于c語言多態(tài)的實現(xiàn),請查看c語言面向對象基礎)。這樣一來可以就避免大量的swith...case和 if...else等條件分支語句,提高程序的可維護性和可擴展性。

下面我將使用獨家總結的六步法,幫助大家輕松掌握狀態(tài)模式(狀態(tài)機)的編程訣竅。

#include <stdio.h>

/***********************************************
1、定義狀態(tài)接口,以MP3的狀態(tài)接口為例,每種狀態(tài)下都可能發(fā)生
兩種按鍵動作。
************************************************/

typedef struct State{
void (* stop)();
void (* palyOrPause)();
}State;


/***********************************************
2、定義系統(tǒng)當前狀態(tài)指針,保存系統(tǒng)的當前狀態(tài)
************************************************/

State * pCurrentState;


/***********************************************
3、定義具體狀態(tài),根據(jù)狀態(tài)遷移圖來實現(xiàn)具體功能和狀態(tài)切換。
************************************************/

void ignore();
void startPlay();
void stopPlay();
void pausePlay();
void resumePlay();

//空閑狀態(tài)時,stop鍵操作無效,play/pause會開始播放音樂
State IDLE = {
ignore,
startPlay
};

//播放狀態(tài)時,stop鍵會停止播放音樂,play/pause會暫停播放音樂
State PLAY = {
stopPlay,
pausePlay
};

//暫停狀態(tài)時,stop鍵會停止播放音樂,play/pause會恢復播放音樂
State PAUSE = {
stopPlay,
resumePlay
};

void ignore()
{
//空函數(shù),不進行操作
}

void startPlay()
{
//實現(xiàn)具體功能
printf("開始播放音樂\n");
//進入播放狀態(tài)
pCurrentState = &PLAY;
}
void stopPlay()
{
//實現(xiàn)具體功能
printf("停止播放音樂\n");
//進入空閑狀態(tài)
pCurrentState = &IDLE;
}

void pausePlay()
{
//實現(xiàn)具體功能
printf("暫停播放音樂\n");
//進入暫停狀態(tài)
pCurrentState = &PAUSE;
}

void resumePlay()
{
//實現(xiàn)具體功能
printf("恢復播放音樂\n");
//進入播放狀態(tài)
pCurrentState = &PLAY;
}


/***********************************************
4、定義主程序上下文操作接口,主程序只關心當前狀態(tài),不關心狀態(tài)之間
是怎么變化的。
************************************************/

void onStop();
void onPlayOrPause();

State context = {
onStop,
onPlayOrPause
};

void onStop(State *pThis)
{
pCurrentState->stop(pThis);
}

void onPlayOrPause(State *pThis)
{
pCurrentState->palyOrPause(pThis);
}


/***********************************************
5、初始化系統(tǒng)當前狀態(tài)指針,其實就是指定系統(tǒng)的起始狀態(tài)
************************************************/

void init()
{
pCurrentState = &IDLE;
}

/***********************************************
6、主程序通過上下文操作接口來控制系統(tǒng)當前狀態(tài)的變化
************************************************/
void main()
{
init();
context.palyOrPause();//播放
context.palyOrPause();//暫停
context.palyOrPause();//播放
context.stop();//停止
}

代碼已經在c在線工具|菜鳥工具中運行驗證,讀者也可以自行驗證。運行結果如下:

開始播放音樂
暫停播放音樂
恢復播放音樂
停止播放音樂

對比前后兩份代碼,六步法實現(xiàn)的狀態(tài)機比簡單狀態(tài)機明顯有以下幾方面的優(yōu)點:

  • 代碼結構要更加清晰,避免了過多的switch...case或者if...else語句   的使用。

  • 很好地體現(xiàn)了開閉原則和單一職責原則,每個狀態(tài)都是一個子結構體,你要增加狀態(tài)就要增加子結構體,你要修改狀態(tài),你只修改一個子結構體就可以了。

  • 封裝性非常好,狀態(tài)變換放置到子結構體的內部來實現(xiàn),外部的調用不用知道子結構體的內部如何實現(xiàn)狀態(tài)和行為的變換。

最后跟大家總計一下狀態(tài)機六步法:

(1)、定義狀態(tài)接口。
(2)、定義系統(tǒng)當前狀態(tài)指針。
(3)、定義具體狀態(tài),根據(jù)狀態(tài)遷移圖來實現(xiàn)具體功能和狀態(tài)切換。
(4)、定義主程序上下文操作接口。
(5)、初始化系統(tǒng)當前狀態(tài)指針。
(6)、主程序通過上下文操作接口來控制系統(tǒng)當前狀態(tài)的變化。

一般來說,熟練使用狀態(tài)機六步法的嵌入式開發(fā)者,大都是兩年軟件開發(fā)經驗以上的老鳥了。所以,如果你還是個嵌入式新手,請在實際開發(fā)中多多運用它,以后你的代碼才能越來越優(yōu)雅美觀。而且掌握狀態(tài)機編程對理解其他更復雜的設計模式也是大有裨益的。

    如果你認為文章不錯,歡迎分享給身邊的同事好友


猜你喜歡

C語言設計模式--簡單工廠模式

C語言、嵌入式中幾個非常實用的宏技巧

C語言、嵌入式應用:TCP通信實例分析

C語言、嵌入式重點知識:回調函數(shù)

C語言、嵌入式位操作精華技巧大匯總

最后

若覺得文章不錯,轉發(fā)分享、在看,也是我們繼續(xù)更新的動力。

在公眾號內回復更多資源,可免費獲取嵌入式資料。期待你的關注~


加好友,回暗號【嵌入式大雜燴】,進微信群


免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術公司SODA.Auto推出其旗艦產品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關鍵字: 汽車 人工智能 智能驅動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網易近期正在縮減他們對日本游戲市場的投資。

關鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產業(yè)博覽會開幕式在貴陽舉行,華為董事、質量流程IT總裁陶景文發(fā)表了演講。

關鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權最終是由生態(tài)的繁榮決定的。

關鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經營業(yè)績穩(wěn)中有升 落實提質增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質量發(fā)展策略,塑強核心競爭優(yōu)勢...

關鍵字: 通信 BSP 電信運營商 數(shù)字經濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術學會聯(lián)合牽頭組建的NVI技術創(chuàng)新聯(lián)盟在BIRTV2024超高清全產業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術創(chuàng)新聯(lián)...

關鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關鍵字: BSP 信息技術
關閉
關閉