86
微軟
Office
從工作流狀態機實踐中總結狀態模式使用心得
作者:banq 發表時間:2003年12月07日 00:10
回複
原貼網址: https://www.jdon.com/jivejdon/thread/10981.html
狀態模式好像是很簡單的模式,正因為狀態好像是個簡單的對象,想複雜化實現設計模式就不是容易,誤用情況很多。
我個人曾經設計過一個大型遊戲係統的遊戲狀態機,遊戲狀態可以說是遊戲設計的主要架構,但是由於係統過分複雜
和時間倉促,並沒有真正實現狀態模式。
目前在實現一個電子政務項目中,需要進行流程狀態變化,在電子政務設計中,我發現,如果一開始完全按照工作流
規範開發,難度很大,它和具體項目實踐結合無法把握,而且工作流規範現在有wfmc,還有bpml,選擇也比較難。因
此,我決定走自創的中間道路。
因為,我需要做一個狀態機API,或者說狀態機框架,供具體係統調用:類如公文流轉應用或信息發報送應用等。
好的狀態模式必須做到兩點:
1. 狀態變化必須從外界其它邏輯劃分出來。
2. 狀態必須可方便拓展,對其它代碼影響非常小。
要做到這兩點,必須先明確狀態變化機製,狀態變化實際是由Event事件驅動的,可以認為是Event-condition-State,
在MVC模式一般是Event-condition-Action實現。狀態模式需要封裝的是Event-condition-State中的condition-State
部分。
清晰理解狀態和流程的關係也非常重要,因為狀態不是孤立的,可以說和流程是點和線的關係,狀態從一個方麵說明
了流程,流程是隨時間而改變,狀態是截取流程某個時間片。因此,必須明白使用狀態模式實現狀態機實際是為了更
好地表達和說明流程。
狀態和流程以及事件的關係如下:
|Event
___currentState__|______newState___
圖中表示了是事件改變了流程的狀態,在業務邏輯中,經常發生的是事件,如果不使用狀態模式,需要在很多業務邏
輯處實現事件到狀態判定和轉換,這有很多危險性。
最大的危險是係統沒有一個一抓就靈的主體結構,以那個遊戲係統為例,在沒有狀態模式對狀態提煉的情況下,狀態
改變由每個程序員想當然實現,導致每個程序員開發的功能在整合時就無法調試,因為這個程序員可能不知道那個程
序員的代碼在什麼運行條件下改變了遊戲狀態,結果導致自己的代碼無法運行。
這種現象實際上拒絕了項目管理的協作性,大大地拖延項目進度(程序員之間要反複商量討論對方代碼設計)。從這
一點也說明,一個好的架構設計是一個項目快速成功完成的基礎技術保證,沒有這個技術基礎,再先進的項目管理手
段也是沒有效率的,或者是笨拙的。
狀態模式對於很多係統來說,確實是架構組成一個重要部分。
下麵繼續討論如何實現一個好的狀態模式,為了實現好的狀態模式,必須在狀態模式中封裝下麵兩個部分:
1. 狀態轉換規則(行為)
2. 狀態屬性(屬性)
狀態轉換行為有兩種劃分標準:
1. run和next兩個行為,run是當前狀態運行行為,next是指在Event參與下,幾種可能轉向的下一個狀態。
2. stateEnter和stateExit, 狀態進入和狀態退出。
如果用進入一個個房間來表示狀態流程的話, 第一種分析是隻重視著“在房間裏”和“如何轉入下一個房間”,這兩種行
為一旦確定,可以被反複使用,進而一個流程的狀態切換可以全部表達出來。
第二中分析方法有所區別,隻重視進入房間和離開房間這個兩個行為,同樣,這種模型也可以被反複利用在其它房間,
一個流程的狀態切換也可以全部表達出來。
具體選擇取決於你的需求,比如,如果你在進入一個狀態開始,要做很多初始化工作,那麼第二種模型就很適合。
狀態變化都離不開一個主體對象,主體對象可以說包含狀態變化(行為)和狀態屬性(屬性),假設為StateOwner,
StateOwner有兩個部分組成:Task/事情和狀態。任何一個Task/事情都會對應一個狀態。
這樣,我們已經抽象出兩個主要對象:狀態State和StateOwner。
為了封裝狀態變化細節,我們可以抽象出一個狀態機StateMachine來專門實現狀態根據事情實現轉換。
這樣,客戶端外界通過狀態機可訪問狀態模式這個匣子。在實踐中,外界客戶端需要和狀態機實現數據交換,我們把
它也分為兩種:屬性和行為。
其中屬性可能需要外界告訴狀態狀態變化的主體對象Task,解決狀態的主人問題,是誰的問題;行為可能是需要持久
化當前這個主體對象的狀態到數據庫。
這兩種數據交換可以分別通過StateOwner和StateMachine與整個狀態機實現數據交換,這樣,具體狀態和狀態切換也
和外界實現了解耦隔離。
因此好的狀態模式實現必須有下列步驟:
(1)將每個狀態變成State的子類,實現每個狀態對象化。
(2)在每個狀態中,封裝著進入下一個狀態可能規則,這些規則是狀態變化的核心,換句話說,統一了狀態轉換的規則。
具體可采取run和next這樣的轉換行為。
下麵是一個子狀態代碼:
public class Running extends StateT{
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月08日 11:16 |
回複 |
|
|
我原來的想法是使用 Event 為中心的處理機製: Event + Listener
拿你說的從一個房間進入另一個房間來說: 臨界事件,就是跨過門。所以,在係統中,一般會有三個處理方法:
prePassThroughDoor(); doPassThroughDoor(); postPassThroughDoor();
在每個事件,都可以加入相應的 Listener. 這樣可以做到係統的可擴展性。
但是這有個問題,就是所有的事件都必須去手寫實現,這會很麻煩。 現在有 AspectJ, 如果我對 AspectJ 的理解沒錯的話,它很容易就可以實現這一點。你可以在任一方法調用之前,或之後,加入相應的事件點,在事件點上加入 Listener 機製。
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月08日 11:34 |
回複 |
|
|
> public class Running extends StateT{ > > // > public void run(StateOwner stateOwner){ > stateOwner.setCurrentState(this); > } > > //轉換到下一個狀態的規則 > > //當前是Running狀態,下一個狀態可能是暫停、結束或者強 > 仆順齙?> //狀態,但是絕對不會是Not_Started這樣的狀態 > //轉換規則在這裏得到了體現。 > public State next(Event e) { > if(transitions == null){ > addEventState(new EventImp("PAUSE"), new > ), new Suspended()); > addEventState(new EventImp("END"), new > ), new Completed()); > addEventState(new EventImp("STOP"), new > ), new Aborted()); > } > return super.next(e); > } > > } > > > > 外界直接調用 > StateMachine的關鍵方法transition;實行狀態的自動轉變。 對於return super.next(e)的處理方式不是很清楚,可否給出StateT的next方法的代碼?
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月08日 18:00 |
回複 |
|
|
StateT的next方法如下:
public State next(Event e) throws InvalidStateException{ logger.debug(
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月09日 11:45 |
回複 |
|
|
我理解Suspended、Completed、Aborted類也都是StateT類的子類,不知是不是這樣? 在StateT類的next方法中,是根據事件來進行跳轉,假設可能又兩個狀態都存在PAUSE事件,又怎麼區分呢? 在剛看到方案時我的感覺是StateT子類中next方法應為return super.next(this,e),不知是不是我的理解有問題?
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月09日 12:19 |
回複 |
|
|
如果你的狀態State非常多,而且Event也非常多,並且每一個Event都可能在很多個不同的State中起作用並使係統進入到不同的State,那麼,是不是需要在每一個State的next()方法中都要用代碼寫出來呢?
另外假如對於 >(StateA)---[Event1]--->(StateB) 過了一段時間需要>(StateA)---[Event1]--->(StateC)那麼又要到代碼裏麵去修改了?
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月09日 14:09 |
回複 |
|
|
to chenye99 你的理解基本沒錯,從實際角度考慮,沒有兩個狀態存在同樣事件的,因為事件相當於外界給予能量,主體有了新能量,本身能量會發生變化,這類似能量守恒定律。
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月09日 14:27 |
回複 |
|
|
to wild fox 狀態模式隻關注某個狀態,以及這個狀態可能轉入的下幾個狀態。
我們不必在乎一個流程有很多狀態,一個個分開對付就可以了。
對於 >(StateA)---[Event1]--->(StateB) 改變到 >(StateA)---[Event1]--->(StateC)
我們所要改變的隻是StateA這個類的next這個方法代碼。當然,也可以將next方法中可能對應的下一個狀態配置在配置文件中,這樣就不需要改代碼了。
重要的是,我們使用狀態模式封裝了狀態和狀態轉換規則,使他們沒有互相混亂地糾纏在一起。
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月09日 18:00 |
回複 |
|
|
我現在在做的項目就是采用的State + StateMachine模式的,不過我們采用了配置文件來設置源State和目標State之間的切換,以次來增加係統的靈活性,雖然有一定的效果,可是,我們不但需要關心當前State,驅動事件,還需要考慮源State,以及當前State的一些條件來決定目標State,可是各個State中還是有很多if...else if...else... ,而且,當源State 和目標 State之間的切換規則發生了變化時,修改了配置文件以後,往往還是需要對相應的代碼進行修改,怎樣做才能在State間的切換規則發生改變以後盡可能地少對代碼進行修改呢?
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月10日 08:44 |
回複 |
|
|
“以及當前State的一些條件來決定目標State”可不可以理解為當前state也存在不同的狀態?如果是這樣,是否有將狀態進一步細化的可能?
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月10日 09:18 |
回複 |
|
|
to wild fox 你們存在的這個情況,說明沒有真正用好狀態模式,這再一次說明,模式用好真的不容易。
用好狀態模式的前提必須有對係統狀態和事件最本質的認識和抽象。
但就我舉的這個實例,next方法中實際是給一個Hashmap賦值,所以,這個過程完成可以使用XML配置。
要做到方便的XML配置,前提還是必須將狀態和狀態轉換規則獨立分離開,否則XML配置將隻是一種形式。
XMl配置是解耦後跟進一步的表現,如果你的邏輯能夠做到XML配置即可運行,那麼說明你之前的解耦設計已經非常成功。
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月10日 09:19 |
回複 |
|
|
to chenye99 狀態有子狀態,類似樹形結構。使用狀態模式處理子狀態會更複雜。
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2003年12月26日 17:40 |
回複 |
|
|
EventImp與 Event的關係,請說一下,我不是太明白?
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2004年09月24日 10:03 |
回複 |
|
|
那位老兄說得很對啊,你也知道1個事件是由一個事物驅動,一個事物同一時間隻有一種狀態,一個狀態卻可能對應0到n個事物,如果你存儲狀態改變的數據結構沒有相關事物信息,將無法區分這個狀態是A事物驅動的還是B事物驅動的,換句話說,這個數據結構的主鍵是:事物id,狀態id,而不是一個狀態id就可以的,不然必定違反數據庫原理!
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月26日 09:47 |
回複 |
|
|
您好,請問banq,StateOwner類的源代碼是什麼?對StateOwner的作用不甚理解.謝謝
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月26日 09:54 |
回複 |
|
|
to banq,banq老師,能否將您上次講的公文流轉的例子講的再詳細些,目前正在做些這方麵的嚐試,很多地方理解的不太好.特別是StateOwner這個對象的作用不是太理解,能否給出代碼並解答,實在是不勝感激啊!
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月26日 10:06 |
回複 |
|
|
具體研究可看看工作流方麵的東西,基於狀態模式,但是更大,有個開源osworkflow還是不錯。
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月26日 10:41 |
回複 |
|
|
to banq, 謝謝banq老師的這麼迅速的回複。有個疑問還想請教您。我覺得各個狀態轉換應該有用戶的參與,在上麵的討論中,用戶好象沒有提及(還是被Event代替?即使是被event代替的話,event中也應該要包含用戶信息啊,不知這樣的理解對不對?)。如果用戶信息要被加入,是放在State的各個子類中還是放在Event裏?是設置為一個actor還是一個role?謝謝!
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月27日 10:25 |
回複 |
|
|
>狀態態轉換應該有用戶的參與 注意耦合,狀態模式的特點就是用事件作為輸入信號,狀態作為輸出信號,而事件的發生則是由用戶觸發的:
用戶---->事件--->狀態。
所以用戶離我們討論的狀態模式很遠,不用拉進來。
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月28日 09:26 |
回複 |
|
|
to banq, 謝謝banq老師。banq老師在前麵談到怎樣將狀態機與應用邏輯解耦。但在實現時,狀態機又必須跟應用邏輯結合,特別是狀態機進行狀態切換的時候,需要訪問相關的應用數據。比如一個會議室的申請流程,如何實現狀態機與應用數據的交互?
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月28日 09:30 |
回複 |
|
|
to banq, 是通過事件或參數來向實例化後的狀態機傳遞相關數據嗎?如果是,還請banq老師詳加指點一二。謝謝
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月28日 10:12 |
回複 |
|
|
你的問題是一種實際中經常遇到的解耦問題:
本帖開始時的代碼transition中一段代碼 State currentState = readCurrentState(taskid); //從數據庫獲得當前狀態
實際就是和業務邏輯發生關係,通過依賴或關聯關係可和業務邏輯層進行交互,當然,之間最好是麵向接口的。
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月28日 21:40 |
回複 |
|
|
通過banq老師的多次指點,今天終於在前麵介紹的思想和部分代碼的基礎上實現了一個非常簡單的工作流狀態機,是一個有關會議室申請的流程,在這裏非常感謝banq老師和其他的jdon道友。但同時也對前麵一個道友談到的觀點深有體會,就是狀態太多了。(我設計了一個State,下麵有許多具體子類,比如Committed,Passed,Finished之類),如下圖。
 有沒有更好的方法?另外,比如公文發文流程,它一般有擬稿、核稿、批準、歸檔等狀態,這些狀態是不是又要重寫?還是可以複用會議室申請的那個工作流狀態機?也就是說可不可以抽象出一個一般工作流狀態機,然後根據具體應用邏輯,在實例化成會議室申請狀態機、公文發文狀態機、物品領用狀態機之類的?
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月28日 21:44 |
回複 |
|
|
這裏特別想請教一下的是,對公文發文流程中的會簽操作,該怎麼處理?感覺很難啊,敬請指教!
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月29日 09:41 |
回複 |
|
|
狀態機可以細化,分類成很多。
圖畫得不錯,如果使用UNML的狀態圖就不感覺那麼“飛舞”了
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2005年12月29日 15:06 |
回複 |
|
|
to banq, 我用的是visio的數據流狀態圖,本來是準備用visio裏麵的UML狀態圖的。還請banq兄不要避重就輕,對我的疑問要詳加解釋才是啊,哈哈!
|
|
|
Re: 從工作流狀態機實踐中總結狀態模式使用心得 |
發表: 2006年04月28日 11:43 |
回複 |
|
|
但對於下列情況該如何描述: 假設一個對象處於正在執行狀態,其要做的事是與另一個係統通信,這個通信可能成功也可能失敗,如果成功,該對象應處於完成狀態,如果不成功,該對象應該處於失敗狀態。 這種情況如果用單一的FROM_STATE--EVENT--TO_STATE該如何表達? |
|
|
最後更新:2017-04-02 00:06:24