1. 程式人生 > >關於狀態機的一點心得體會

關於狀態機的一點心得體會

使用狀態機的好處:
1. 有限狀態機(FSM)是收斂的,保證使用者操作(外部事件)都能夠響應,並且回到可以預測的可控制範圍內。
2. 狀態機是正交的,可以覆蓋使用者操作(外部事件)的所有可能性,而又不會重複處理。
3. 另外,狀態機作為一個通用的表現形式,有時候還能夠在某一個抽象層次上作為一個可以複用的模板。比如高通的CDMA程式碼中,實現ICMP協議和LCP協議的時候就抽象出了一個通用的FSM。

既然狀態機有這樣的好處,為什麼不是所有的程式都用狀態機的形勢表示和處理呢?這是因為“有限”兩個字,如果都用狀態機表示,勢必造成狀態的無限擴充套件,反爾不好處理。比如,在輸入密碼的介面,如果每輸入一個密碼,也認為是一個新的狀態,那麼……

另外,狀態機表現世界也不是萬能的,比如狀態機不具有記憶性(僅能記憶當前的內容),所以,需要配合其它的結構,比如棧等。比如,在brew程式裡,使用棧來記憶App獲得焦點的順序。

怎樣比較好的實現一個狀態機:
狀態機的一般表現形式:
     event / action
A -------------------------> B

action 又可以分為:
1) 接收到event時做的事情,這個時候還不發生狀態的改變, s_ev_handle;
2) 狀態改變時,從A退出做的動作, exit_action;
3) 狀態改變時,進入B做的動作, enter_action。

這樣的分法有這樣的好處:
1)有的時候,一個狀態在等到某一個事件時,會進行一次長時間的,或者是特定的處理,或者處理完成並不進行狀態的轉移,這個時候用s_ev_handle進行特別處理比較合理。
2) 有的時候,進入或退出一個狀態都會進行一些公共的處理,例如初始化或者回收狀態期間的臨時物件等,比如,在mot的UI程式中,建立或POP該狀態對應的FORM;註冊某些特定狀態下才會響應或不響應的事件等,這個時候用enter_action或exit_action比較合理。
3) 狀態轉移應該是個比較快的過程,這樣才能滿足時間上狀態機的正交性,所以enter_action和exit_action應該處理一些耗時比較短的動作。
4) 通過在狀態初始化時進行enter_action的動作,狀態中進行s_ev_handle的處理,狀態退出時進行exit_action的動作,一個狀態就有了自己的生命週期,這個在C程式設計中似乎只是形式上的東西,沒有什麼意義,但是通過這種OO的思考方式應該對設計有所幫助。

下表是一個狀態機的通用表現形式

ev1 ev2 ev3
s1 (enter_action, exit_action)  s1_ev1_handle
s2
s3

在mot目前的程式碼框架中,brew_comm就是通過遍歷每個App的狀態表實現的,brew_comm的event_handle函式的第一次迴圈是按行遍歷了狀態表的第一列,找到了當前的App狀態,再按列遍歷找到event響應的處理函式。
另外,brew_comm還提供了app_change_state的方法,實現狀態轉移時enter_action, exit_action的呼叫。

運用狀態機有可能遇到的問題:
有的時候,會發現enter_action的動作中需要上一個狀態的資訊,比如按CLR鍵進入前一個FORM時有可能需要知道是從哪個FORM退回的,那是不是要把前一個FORM的enter_action的動作加到s_CLR_handle中去呢?我覺得這裡有兩個思路可以解決這個問題,1) 利用別的資料結構來克服狀態機缺乏記憶功能的問題,例如使用一個變數儲存s_CLR_handle的狀態標識;
2) 索性把新狀態分成幾個狀態(或者子狀態),因為從狀態機描述客觀世界的角度看,處理流程的不一致,其實就是產生了新的狀態。不過這個方法好像和目前mot的框架思想不太一致,因為這樣可能會產生這樣的結果:一個FORM會對應若干個狀態。

一點建議:
在設計到編寫程式碼之前,建議填寫完整上面的狀態表格,能夠集中檢查設計中的不足;統一確定各個事件,狀態,處理函式的命名規則,使程式具有較好的可維護性;能夠利用第三方工具直接生成程式碼,進行測試,或者反對映成狀態圖等。

需不需要在一個App進入和退出的時候加兩個不需要FORM對應的偽狀態?這樣可以解決下面一些問題:
1)比如在進入App的第一個狀態前,App需要根據一個事件才能確定進入哪個狀態,如果加了初始的偽狀態,App就可以在偽狀態中等待觸發事件了。
2) App可以從很多狀態退出,那麼這些狀態都需要呼叫App的退出處理過程,這時,加一個結束偽狀態更加可以精簡程式碼,並且提高狀態機的收斂性和完備性。