狀態
本文只討論計算機里面的狀態,並且只是討論對象,對象其實是抽象的產物,所以狀態也取決於我們是如何對對象進行抽象和建模的,根據建模方法不同,對象也不同。對象分為有狀態的對象和無狀態的對象,無狀態對象特指那種特性形態固定不變的對象,通常他們在面向對象領域都是單例的,而有狀態對象不同,有狀態對象通常是多例的。舉個例子
- 有狀態對象:Session對象,Session的概念在很多系統中會存在,因為它對每一次會話都會建立該次會話的特征
- 無狀態對象:UserFactory對象,作為一個工廠對象,通常就是無狀態的
以前的老系統EJB,有激活和鈍化的概念,但現在的編程系統都基本自己負責實現有狀態對象的存儲了,DDD中有一個概念就是rebuild模式,就是對象的重建。簡單的狀態是不需要關注的,但是如果要根據狀態進行驅動,那么就要對狀態進行管理,下面介紹幾種狀態驅動的模型,第一種是普通的狀態,第二種是狀態機,第三種是有限Action的狀態管理器
普通枚舉狀態
這種方式的狀態是最簡單的對象狀態表示,我們時不時都會用上,例如你會在一個實例類中加一個字段:boolean isFinished ;用作表示對象是否已經是完成狀態,或者:boolean isRunning;用做表示對象是否在運行狀態,這種狀態一般都是可枚舉完的,而且不像狀態模式是狀態之間的扭轉,而是由調用者設置屬性狀態,例如 obj.setFinished ;
注意,你可以不必拘泥於一種方式組織枚舉狀態,你也可以用一個棧組織這些狀態,例如:
/** * * 代表一個對話管理的目標狀態 * **/ private Stack<Goal> interGoal = new Stack(); public void addGoal (Goal newGoal){ interGoal.push(newGoal); } public void doGoal (){ Goal currentGoal = interGoal.peek(); currentGoal.doGoal; interGoal.poll; }
如上,對象的狀態是對話目標,對話目標可以用棧來表示,這樣的好處是:狀態可以記錄,回退;所以狀態的表示,只需要你巧妙利用數據結構,就能達到一些不可思議的效果。
FMS 有限狀態機
狀態機是相對一個系統而言的,其實准確來說,是一個比較核心的對象而言,這個對象的職責和功能,是通過狀態機這種數學模型建模得以運作。例如我們的聚合根對象。
狀態機,很多文章介紹會說直接等同於有限狀態機(FMS),FSM 解決一個輸入序列,經過 FSM,最終停留在什么狀態這樣一個問題。比較注重的是系統(對象)的狀態,和狀態之間的轉換,狀態是歷史輸入的一種結果,對於一個系統而言,狀態機基本包含以下幾種模型:
- 事件(Event)
- 動作(Action)
- 狀態(State):現態,次態
- 轉換
狀態 State:狀態機的狀態是有限的,例如一個機器人對話系統的Session對象,就可以抽象出幾種狀態:
- 初始狀態;
- 思考決策狀態;
- 動作執行狀態;
- 待用戶回復狀態;
- 待服務返回狀態;
- 結束狀態。
而當前時刻,Session只能處於一個狀態中;狀態轉換前后,前一個狀態稱為現態,后一個狀態稱為次態。
事件 Event:狀態機通常是通過事件驅動運行的,事件代表狀態機可以處理的事件,通常發送一件事后,會把事件類型和事件內容一並傳遞給狀態機。狀態機接收到事件消息后,調用相應的動作去處理該事件,處理結果可能會是現態到次態的轉換。
動作 Action:狀態機是如何響應不同的事件的呢,其實每一個狀態,都會遇到不同的事件,所以對不同的事件,也是有不同的處理的。比喻在待服務返回狀態,發生了待用戶說要轉換意圖的事件,那么事件就是《轉換意圖》,事件內容是《取消訂票》,那么就需要執行 《初始化意圖決策流程》這個動作,然后把狀態轉換為《思考決策狀態》。通常事件和狀態可以組合為一張表:
屬性 | 初始狀態 | 思考決策狀態 | 動作執行狀態 | 待用戶回復狀態 | 待服務返回狀態 | 結束狀態 |
新意圖事件 | Action1 | Action2 | —— | —— | —— | Action3 |
服務返回事件 | —— | —— | Action2 | Action1 | —— | Action1 |
用戶返回事件 | —— | Action1 | —— | —— | Action2 | —— |
服務異常事件 | —— | —— | Action3 | —— | —— | Action3 |
狀態轉換:介紹完動作和事件后,我們就清楚轉換是怎么得到的了,也就是狀態 s1 接收到事件 event1 后,執行了某些動作 action1,然后還把狀態轉換為了 s2,下圖是機器人Session對象的狀態轉換圖。
TCP 連接協議狀態
下面給出一個TCP協議的有限狀體機的示例,該例子估計大家都看得明白,Client客戶端和Server服務端,都對應有自己的狀態State和動作Action,分別對應着事件Event和轉換
狀態:closed、syn_sent、established、fin_wait1、fin_wait2、time_wait、listen、syn_received、close_wait、last_act
以上狀態區別,一般還按照自身是發起端還是主動關閉端區分,只有close和established兩種狀態是兩個端共有的。
事件:事件即該端口接收到網絡的請求,sys\ack\fin等,這里還有事件附帶的數據,例如syn =a ,里面的a就是數據,我們一般稱之為 payload
動作和狀態轉換關系具體可參考下圖:
狀態策略
狀態模式是設計模式的內容,但和狀態機不態一樣。一個類如果有狀態,那么其狀態的表示是非常多的,而狀態模式很多時候就用一個狀態類代表其所處的狀態。有時候你需要的系統的狀態是很難窮舉的。所以狀態模式是一種比較低級的應用了。
無限的狀態,有限的Action ——》狀態策略:通常,我們的對象建模很難做成狀態機,因為狀態很可能是無限的,但不管如何,我們的系統執行動作是有限的,也就是State是無限的,而Action是有限的,畢竟Action需要我們程序員手寫去實現,那么無限的狀態,如果對應這一個個有限的Action呢,我們可以抽象一個根據當前狀態選擇Action的算法,我們把該算法抽象為<狀態策略>。在介紹狀態策略之前,最好大家先學習一個框架Redux
Redux:首先介紹下Redux,前端框架的一種,這個自稱是狀態機實現,每一個組件都是狀態機,這個是我認為最合適作為狀態機定義的實現,因為React接受外界輸入action后,會根據state重新渲染組件,所以會帶有觀察者模式的感覺,所以我認為它更接近狀態機的本質 —— 狀態驅動
下面一段redux的小demo,代表整個redux的工作原理
function createStore(reducer, preloadedState) { let state = preloadedState let listeners = [] function subscribe(listener) { listeners.push(listener) } function getState() { return state } function dispatch(action) { state = reducer(state, action) for (listener of listeners) { listener() } } /**通過一個不匹配任何 reducer的 type,來全部的初始值*/ /**redux 中是使用一個隨機的字符串來保證不匹配任何 reducer, 這里使用了 ES6 的 Symbol 類型*/ dispatch({ type: Symbol() }); return { subscribe, getState, dispatch } }
策略:Policy,是一種Action選擇的算法實現,它的輸入是當前狀態,輸出是動作 Action,策略可以用規則實現,也可以用機器學習等算法實現。我們現場定義一個策略的接口
public class Policy{ // 根據當前狀態,選擇處理這個狀態的策略 Action chooseAction(State state); }
有了接口,我們就不用擔心具體的實現,只需要執行Action即可。而Action的執行,當然是交給狀態管理器了。隨着狀態管理器執行完Action,Action會改變狀態,也會出發觀察者,所有外界就得到通知,外界處理完后會回饋到系統中,系統收到反饋后,這里注意的是,Action會收到這種反饋,然后改變狀態State,當State改變后,系統可以把新的State傳遞給Policy,然后繼續這個良性循環,從而整個狀態機,得到了驅動。
狀態管理器:Redux就是一個狀態管理器,但是比較無奈的是,它接受的輸入是外界的Action,使得狀態改變,然后通知所有狀態的觀察者,這種模式的實現,也只有前端比較合適應用了,下面我給大家再介紹一個例子,那就是對話系統的例子,DST(Dialog State Tracker)和DP(Dialog Policy),前者為對話狀態跟蹤,后者為對話策略,其中對話策略可以用機器學習算法實現;
狀態機與線程安全
狀態模式,一般情況下,都是一件事一件事發生和處理的,但是有時候我們需要處理的系統並非如此簡單,而是大量的事件在不同的事件發生,這個時候,我們可以有以下幾種建模方式:
1、單線程:這種最簡單,一個狀態機維護一個事件隊列,所有事件按照時間的順序入隊,然后用單一的線程處理該隊列的事件,這樣可以保證每個事件的事務性,但缺點也是有的,處理速度會變得更慢,而且會導致無事件的時候,線程閑置,優點是簡單,而且可以給事件作優先級隊列,使得重要的事件可以優先處理。
2、多狀態:如果事件數量較少,我們可以把事件的先來后到的所有組合情況的狀態都抽象出來,每一個事件的組合發生順序都有一個對應的狀態,優點是事件可以多線程,但缺點是狀態會過多,難維護,所以適合事件數量較少的情況,最好是兩三個事件。
3、保持冪等:狀態按照業務需求設計,讓狀態機可以多線程運作事件,但也並非沒有同步機制,可以用狀態的改變的原子性同步,會有個缺點就是可能會在狀態切換的時候發生ABA問題,但ABA也可以理解的,畢竟事件都有權利按照自己的事務執行,所以在action的設計中,要特別的注意相關所有狀態對象的冪等處理,保證同一個消息被狀態機處理多次,效果是相同的,也就是包裝action獨立於事件即可。