括號里面是本文的副標題,跟著作者本來是想搞明白QP是如何實現有限狀態機的,結果萬萬沒想到,其實作者的目的不純,他不光想讓搞明白QP的前世,還想讓你的內功心法(C語言)在實戰中進階(好文不多,建議大家讀一下原著,或許才能體會到作者的良苦用心,大部分書籍只是教你招式,讓你感悟心法的并不多,感謝作者)。
下面還是由一個書中提到的定時炸彈的例子開啟我們的侃大山模式,話說一個定時炸彈都有哪些功能,你能想到的:
1.他得能倒計時,時間到,一聲 bomb~!!!
2.要倒計時 ,得有個定時時間 ,timer。
3.有了定時時間,得支持定時功能吧 ,得有UP/DOWM按鍵進行timer調整。
4.有了定時模式,你得切換到倒計時模式吧,這里有ARM按鍵,按下開啟倒計時。
5.我還想讓這個炸彈有意思一點,給人一定的機會,提供一個炸彈解除密碼吧(友情提示,剪線直接炸給你看~!)
6.設置一個初始密碼,你可以通過上下按鍵進行移位輸入密碼,輸入完成按ARM鍵,這個時候如果輸入正確,那么炸彈解除倒計時,進入定時模式(危險解除),如果輸入錯誤,那么也不會立即爆炸 ,可以重新嘗試,畢竟猜對密碼難嘛。
7.有了這一些內容,你還需要以一塊顯示屏,管理倒計時和輸入密碼的顯示,基本就這些了,炸彈的原型就出來了:
如何構建狀態機,一千個人有一千種狀態機設計思路(可能),并沒有標準答案,我總結了一個通用的黃金規則,簡單的情況可以copy規則,直接生成狀態機,復雜的情況還是需要認真的思考,不斷地改進模型,打磨出更完美的設計,如下:
1. 一個項目中需要創建多個狀態機,狀態機的本質是管理資源,什么是資源,內存 IO 鍵盤、顯示器等等,也可以是虛擬的資源,例如狀態機擴展變量,當然一個狀態機不一定只能管理一種資源,但當你不知道該怎么組合的時候,那么就一個狀態機,一個資源,狀態機會多一些,但沒什么問題。
2.一個狀態機會在多種狀態之間進行切換,那么如何定義到底有幾個狀態,很簡單,這取決于對事件的反應不同,有幾種不同的動作就構造幾個狀態,相同的動作就放在一個父狀態里(HSM中)。
這個狀態機很簡單,只需要創建一個狀態機(bomb),兩個狀態setting和timming狀態,再加一個偽狀態(炸了狀態)就實現了,狀態圖如下:
基于這個項目,我們提供三個版本的實現讓我們的C徹底進階:
初級版:
1. 先定義狀態機主體和狀態定義:
2. 定義事件主體和事件定義:
3.一個狀態機還需要三個函數來幫助他實現自身的功能,構造函數,初始化函數和事件分發處理函數,聲明和定義如下:
4.接下來我們來寫一下main.c的核心部分,如下:
到這里整個方案的代碼的核心部分就全了,這里你可以思考一下這么寫的優點和缺點,想想如何改進,想10秒再往下看(答案固然重要,獨立思考更重要),倒計時:
10
想一想優點
9
想一想缺點,別急
8
假如是你,你該如何改進它
7
好了,揭曉我的答案(不一定準確)
654321~!
總結,先說說這么寫的優點:
1.符合我們對于事物的思考方式,你可以很快掌握他是符合運行,沒有太大的難度。
2.他并沒有用太多的復雜的設計方法,例如使用指針,復雜的類型變化,算法優化,不需要工程師太深的基礎就能掌握。
3.基于他的設計方式,你可以很快上手,來改進自己的項目,讓他看起來更加簡潔明了。
接下來我們來說說他的缺點:
1.首先我們看到void Bomb1_dispatch(Bomb1 *me, Event const *e)這個函數的實現有點長,我們知道軟件的設計的規則中,有一條讓你寫的函數盡可能的短小,簡單明了,這里只有兩個狀態,三個事件就已經占了這么大的篇幅了,再加幾個還得了~!
2.第二點,這并不是一個很好的構架,因為不論以后你怎么擴展這個狀態機,你的主戰場都是盯著這個dispatch函數,哪怕你可以把處理封裝成函數,讓它調用,但是它就好像是樹根一樣,每長一片葉子,這個樹干都要做一些處理,這并不是一個很好的構架,從事多年程序設計的人員應該深有體會,慢慢的他會隨著需求變得不再簡單明了,遠離我們的初衷。
片花:我們的初衷,dispatch不應該是這樣,他只是需要傳入一個狀態機和事件,然后把控制權轉交給這個狀態機,等待處理完以后,控制權再回到dispatch函數中,這里可能會涉及一個課題,如何把狀態機從dispatch中剝離出來,所謂的剝離并不是完全獨立,而是讓所有的狀態機可以擁有一個dispatch,而不需要植入任何和自己相關的狀態機代碼,思想上升,如何設計一個通用的dispatch(事件處理器,簡稱QEP,先引出核心課題),下一篇我們開啟進階之路2。