前言
功能業(yè)務(wù)代碼寫(xiě)多了,回看代碼會(huì)發(fā)現(xiàn)用if-else if-else用的越來(lái)越多,因?yàn)橛泻芏鄨?chǎng)景需要區(qū)分,不同場(chǎng)景下的功能不一樣,因此需要通過(guò)if做判斷,場(chǎng)景一旦多了起來(lái)起來(lái),用if-else if-else就會(huì)越來(lái)越多。
這里的if-else if-else通常是用來(lái)區(qū)分不同場(chǎng)景下的功能實(shí)現(xiàn),和上篇提到的表驅(qū)動(dòng)編程還不太一樣。
為了避免這種情況,本編介紹一種開(kāi)發(fā)方法--狀態(tài)機(jī)編程。
為什么需要狀態(tài)機(jī)編程?相信很多人在實(shí)現(xiàn)if判斷的時(shí)候,偶爾會(huì)出現(xiàn)不小心漏掉或者寫(xiě)錯(cuò)一些觸發(fā)條件,導(dǎo)致功能出現(xiàn)異常的問(wèn)題,特別是一些復(fù)雜的邏輯條件,一大堆的&&/||/()等眼花繚亂的復(fù)雜邏輯,寫(xiě)完之后恐怕自己都得檢查好一會(huì),擔(dān)心是不是哪個(gè)條件不能正常觸發(fā)。狀態(tài)機(jī)通常采用?switch-case實(shí)現(xiàn)。
那么狀態(tài)機(jī)的引入解決了哪些問(wèn)題呢?
當(dāng)程序有多個(gè)狀態(tài)時(shí),規(guī)范了程序的狀態(tài)轉(zhuǎn)換,避免了一些引入一些復(fù)雜的判斷邏輯。
規(guī)范了程序在不同狀態(tài)下的實(shí)現(xiàn)和所能提供的能力。
在能力上可以進(jìn)行橫向擴(kuò)展,提供新的狀態(tài)來(lái)完善現(xiàn)有邏輯
邏輯清楚,實(shí)現(xiàn)過(guò)程會(huì)多考慮一些情況,方便定位問(wèn)題所在
介紹
什么是狀態(tài)機(jī)?
狀態(tài)機(jī)是有限狀態(tài)自動(dòng)機(jī)(FSM)的簡(jiǎn)稱,是現(xiàn)實(shí)事物運(yùn)行規(guī)則抽象而成的一個(gè)數(shù)學(xué)模型。
簡(jiǎn)單理解就是:現(xiàn)實(shí)事物是有不同狀態(tài)的,比如燈,就有“亮”和“滅”兩種狀態(tài),再?gòu)?fù)雜抽象一點(diǎn),增加一個(gè)“損壞”狀態(tài),那這屬于異常情況了。
概念
現(xiàn)態(tài):當(dāng)前所處的狀態(tài),一個(gè)狀態(tài)機(jī)至少要包含兩個(gè)狀態(tài),某一時(shí)刻只能是一種狀態(tài),比如剛才說(shuō)到的燈,就有“亮”和“滅”兩種狀態(tài)
條件:又稱事件,執(zhí)行某個(gè)操作的觸發(fā)條件或者口令,比如燈通過(guò)開(kāi)關(guān)控制,操作開(kāi)關(guān)就是一個(gè)事件
動(dòng)作:事件發(fā)生以后要執(zhí)行動(dòng)作,比如開(kāi)關(guān)按下開(kāi)關(guān),燈亮,松開(kāi)則滅
次態(tài):條件滿足后要遷往的新?tīng)顟B(tài),比如開(kāi)關(guān)按下后燈從當(dāng)前滅的狀態(tài)變?yōu)榱恋臓顟B(tài)
用過(guò)"RTOS"的朋友應(yīng)該也知道任務(wù)的幾種狀態(tài),任務(wù)的四種狀態(tài)分別是:就緒態(tài)、運(yùn)行態(tài)、阻塞態(tài)、掛起態(tài),不會(huì)同時(shí)出現(xiàn)兩種及兩種以上的狀態(tài)存在,OS 根據(jù)當(dāng)前的狀態(tài),和任務(wù)優(yōu)先級(jí)、滴答時(shí)鐘、主動(dòng)睡眠等條件進(jìn)行任務(wù)的狀態(tài)切換。
狀態(tài)機(jī)的動(dòng)作類型
進(jìn)入動(dòng)作:在進(jìn)入狀態(tài)時(shí)進(jìn)行
退出動(dòng)作:在退出狀態(tài)時(shí)進(jìn)行
輸入動(dòng)作:依賴于當(dāng)前狀態(tài) 和 輸入條件進(jìn)行
轉(zhuǎn)移動(dòng)作:在進(jìn)行特定轉(zhuǎn)移時(shí)進(jìn)行
實(shí)現(xiàn)
首先,看一個(gè)簡(jiǎn)單的例子,在不同場(chǎng)景下實(shí)現(xiàn)控制電機(jī)的功能:設(shè)備開(kāi)機(jī)啟動(dòng)三次電機(jī)、開(kāi)關(guān)按下一次啟動(dòng)一次、關(guān)機(jī)啟動(dòng)三次電機(jī)。采用非狀態(tài)機(jī)的寫(xiě)法,通過(guò)各種標(biāo)志位去判斷設(shè)備是否需要控制電機(jī),什么條件下退出等。
只是簡(jiǎn)單的實(shí)現(xiàn)一下,可能其中也有一些狀態(tài)機(jī)的思想吧(畢竟?fàn)顟B(tài)機(jī)編程思想已經(jīng)在腦海里,不可避免吧),不過(guò)我還是盡量還原我初次編程期間的實(shí)現(xiàn)這個(gè)功能的邏輯思想吧,勿怪。
?
?
/*?控制電機(jī)函數(shù)?*/ void?MotorCtrlTask(void) { ????if?(ctrlCnt) ????{ ????????MotorCtrl(ON); ????????delay(1); ????????MotorCtrl(OFF); ????} ????else ????{ ????????MotorCtrl(OFF); ????} } int?isPowerOn?=?true; int?isPowerOff?=?false; int?ctrlCnt?=?0; void?main(void) {??? ????while?(1) ????{ ????????if?(isPowerOn) ????????{ ????????????isPowerOn?=?false; ????????????ctrlCnt?=?3; ????????} ????????if?(keyPress) ????????{ ????????????keyPress?=?false; ????????????ctrlCnt?=?1; ????????} ????????if?(...)??//?關(guān)機(jī)條件 ????????{ ????????????if?(ctrlCnt?==?0?&&?!isPowerOff?&&?!isPowerOn) ????????????{ ????????????????isPowerOff?=?true; ????????????????ctrlCnt?=?3; ????????????} ????????} ????????MotorCtrlTask(); ????????if?(ctrlCnt?>?0) ????????????ctrlCnt--; ????????else ????????{ ????????????if?(ctrlCnt?==?0?&&?isPowerOff?&&?!isPowerOn) ????????????{ ????????????????return; ????????????} ????????} ????} }
?
?
通過(guò)采用狀態(tài)機(jī)編程的方式,首先考慮的就是有三種狀態(tài):開(kāi)機(jī)、關(guān)機(jī)和工作狀態(tài),先理清楚三種狀態(tài)之間轉(zhuǎn)換的條件和當(dāng)前狀態(tài)需要執(zhí)行的相關(guān)功能,然而在實(shí)現(xiàn)過(guò)程中就會(huì)意識(shí)到還需要增加一種過(guò)渡狀態(tài):關(guān)機(jī)準(zhǔn)備中(關(guān)機(jī)過(guò)程中需要執(zhí)行的一系列操作)。
只有邏輯清晰了,才會(huì)下意識(shí)的察覺(jué)少了一些東西了,特別是一些臨界處理等。
?
?
int?sysState?=?POWER_OFF;?//?默認(rèn)關(guān)機(jī)狀態(tài) int?ctrlCnt?=?0; /*?控制電機(jī)函數(shù)?*/ void?MotorCtrlTask(void) { ????if?(ctrlCnt) ????{ ????????MotorCtrl(ON); ????????delay(1); ????????MotorCtrl(OFF); ????} ????else ????{ ????????MotorCtrl(OFF); ????} } void?main(void) { ????while?(1) ????{ ????????switch?(sysState) ????????{ ????????????case?POWER_OFF:?//?關(guān)機(jī)狀態(tài) ????????????????sysState?=?POWER_ON;?//?自動(dòng)切換成開(kāi)機(jī)狀態(tài) ????????????????ctrlCnt?=?3; ????????????????break; ????????????case?POWER_ON:??//?開(kāi)機(jī)過(guò)程狀態(tài) ????????????????...?//?開(kāi)機(jī)過(guò)程中的其他功能 ????????????????if?(ctrlCnt?==?0)?//?控制結(jié)束自動(dòng)切換工作狀態(tài) ????????????????{ ????????????????????sysState?=?WORKING; ????????????????????break; ????????????????} ????????????????break; ????????????case?WORKING:??//?工作狀態(tài) ????????????????if?(...)?//?關(guān)機(jī)條件 ????????????????{ ????????????????????sysState?=?POWER_OFF_READY; ????????????????????ctrlCnt?=?3; ????????????????????break; ????????????????} ????????????????if?(keyPress) ????????????????{ ????????????????????keyPress?=?false; ????????????????????ctrlCnt?=?1; ????????????????} ????????????????break; ????????????case?POWER_OFF_READY:??//?關(guān)機(jī)準(zhǔn)備中 ????????????????...?//?關(guān)機(jī)準(zhǔn)備中的其他功能 ????????????????if?(ctrlCnt?==?0)?//?控制結(jié)束自動(dòng)退出 ????????????????{ ????????????????????sysState?=?POWER_OFF; ????????????????????return;?//?退出程序 ????????????????} ????????????????break; ????????????default: ????????????????break; ????????} ????????MotorCtrlTask(); ????????if?(ctrlCnt?>?0) ????????????ctrlCnt--; ????} }
總結(jié):從上述兩份代碼看,你覺(jué)得哪一個(gè)邏輯更清晰呢?非狀態(tài)機(jī)實(shí)現(xiàn)方式還有一些異常處理沒(méi)有實(shí)現(xiàn),比如開(kāi)機(jī)過(guò)程中在啟動(dòng)三次調(diào)節(jié)電機(jī)過(guò)程中按下會(huì)有什么現(xiàn)象呢,為了避免這種情況又需要加入多少if條件判斷呢?
適用場(chǎng)景
狀態(tài)機(jī)應(yīng)用范圍挺廣的,不止是在C語(yǔ)言中,其他都能使用,準(zhǔn)確來(lái)說(shuō)這個(gè)屬于一種編程思想。特別是業(yè)務(wù)功能,狀態(tài)機(jī)是常用的。
比如常常使用的模塊也有狀態(tài)機(jī)的身影:比如按鍵的按下和松開(kāi),按下又包括按下瞬間、多次按下、持續(xù)按下、松開(kāi)瞬間和持續(xù)松開(kāi)等
通常狀態(tài)機(jī)和表驅(qū)動(dòng)可以結(jié)合使用,狀態(tài)機(jī)的現(xiàn)態(tài)、條件、動(dòng)作和次態(tài)作為數(shù)據(jù),執(zhí)行這些狀態(tài)切換的作為邏輯。完全可以根據(jù)實(shí)際情況靈活使用。
代碼參考:
菜單:菜單控制,可以這樣理解:當(dāng)前菜單界面為現(xiàn)態(tài)、菜單進(jìn)入和退出為條件、菜單切換時(shí)的函數(shù)執(zhí)行為動(dòng)作、上下級(jí)菜單理解為次態(tài),其中菜單選項(xiàng)表就靈活使用了狀態(tài)機(jī)和表驅(qū)動(dòng)的方式。
https://gitee.com/const-zpc/menu.git
ESP8266:AT指令數(shù)據(jù)表,包含指令、期望響應(yīng)、時(shí)間和函數(shù)指針【后續(xù)的動(dòng)作】等,可以這樣理解:數(shù)組索引當(dāng)前值為現(xiàn)態(tài)、收到響應(yīng)和超時(shí)為條件、收到響應(yīng)或超時(shí)執(zhí)行的函數(shù)為動(dòng)作、數(shù)組索引的未來(lái)值為次態(tài)
https://gitee.com/const-zpc/esp8266
ESP8266:AT指令數(shù)據(jù)表,包含指令、期望響應(yīng)、時(shí)間和函數(shù)指針【后續(xù)的動(dòng)作】等,可以這樣理解:數(shù)組索引當(dāng)前值為現(xiàn)態(tài)、收到響應(yīng)和超時(shí)為條件、收到響應(yīng)或超時(shí)執(zhí)行.
編輯:黃飛
?
評(píng)論