.NET中的狀態(tài)機(jī)庫(kù)Stateless的操作流程
介紹
什么是狀態(tài)機(jī)和狀態(tài)模式
狀態(tài)機(jī)是一種用來(lái)進(jìn)行對(duì)象建模的工具,它是一個(gè)有向圖形,由一組節(jié)點(diǎn)和一組相應(yīng)的轉(zhuǎn)移函數(shù)組成。狀態(tài)機(jī)通過(guò)響應(yīng)一系列事件而“運(yùn)行”。每個(gè)事件都在屬于“當(dāng)前” 節(jié)點(diǎn)的轉(zhuǎn)移函數(shù)的控制范圍內(nèi),其中函數(shù)的范圍是節(jié)點(diǎn)的一個(gè)子集。函數(shù)返回“下一個(gè)”(也許是同一個(gè))節(jié)點(diǎn)。這些節(jié)點(diǎn)中至少有一個(gè)必須是終態(tài)。當(dāng)?shù)竭_(dá)終態(tài), 狀態(tài)機(jī)停止。
狀態(tài)模式主要用來(lái)解決對(duì)象狀態(tài)轉(zhuǎn)換比較復(fù)雜的情況。它把狀態(tài)的邏輯判斷轉(zhuǎn)移到不同的類(lèi)中,可以把復(fù)雜的邏輯簡(jiǎn)單化。
狀態(tài)機(jī)的要素
狀態(tài)機(jī)有4個(gè)要素,即現(xiàn)態(tài)、條件、動(dòng)作、次態(tài)。其中,現(xiàn)態(tài)和條件是“因”, 動(dòng)作和次態(tài)是“果”。
- 現(xiàn)態(tài) - 是指當(dāng)前對(duì)象的狀態(tài)
- 條件 - 當(dāng)一個(gè)條件滿足時(shí),當(dāng)前對(duì)象會(huì)觸發(fā)一個(gè)動(dòng)作
- 動(dòng)作 - 條件滿足之后,執(zhí)行的動(dòng)作
- 次態(tài) - 條件滿足之后,當(dāng)前對(duì)象的新?tīng)顟B(tài)。次態(tài)是相對(duì)現(xiàn)態(tài)而言的,次態(tài)一旦觸發(fā),就變成了現(xiàn)態(tài)
Stateless
Stateless
是一款基于.NET的開(kāi)源狀態(tài)機(jī)庫(kù),最新版本4.2.1, 使用它你可以很輕松的在.NET中創(chuàng)建狀態(tài)機(jī)和以狀態(tài)機(jī)為基礎(chǔ)的輕量級(jí)工作流。
由于整個(gè)項(xiàng)目基于.NET Standard的編寫(xiě)的,所以在.NET Framework和.NET Core項(xiàng)目中都可以使用。
項(xiàng)目源代碼 https://github.com/dotnet-state-machine/stateless
以下是一個(gè)使用Stateless編寫(xiě)的打電話流程
var phoneCall = new StateMachine<State, Trigger>(State.OffHook); phoneCall.Configure(State.OffHook) .Permit(Trigger.CallDialled, State.Ringing); phoneCall.Configure(State.Ringing) .Permit(Trigger.CallConnected, State.Connected); phoneCall.Configure(State.Connected) .OnEntry(() => StartCallTimer()) .OnExit(() => StopCallTimer()) .Permit(Trigger.LeftMessage, State.OffHook) .Permit(Trigger.PlacedOnHold, State.OnHold); // ... phoneCall.Fire(Trigger.CallDialled); Assert.AreEqual(State.Ringing, phoneCall.State);
代碼解釋
當(dāng)前初始化了一個(gè)狀態(tài)機(jī)來(lái)描述點(diǎn)電話的狀態(tài),這里電話的初始狀態(tài)為掛機(jī)狀態(tài)(OffHook)當(dāng)電話處于掛機(jī)狀態(tài)時(shí),如果觸發(fā)被呼叫事件,電話的狀態(tài)會(huì)變?yōu)轫戔彔顟B(tài)(Ringing)當(dāng)電話處于響鈴狀態(tài)時(shí),如果觸發(fā)通過(guò)連接事件,電話的狀態(tài)會(huì)變?yōu)橐堰B接狀態(tài)(Connected)當(dāng)電話處于已連接狀態(tài)時(shí),系統(tǒng)會(huì)開(kāi)始計(jì)時(shí),已連接狀態(tài)變?yōu)槠渌麪顟B(tài)時(shí),系統(tǒng)會(huì)結(jié)束計(jì)時(shí)當(dāng)電話處于已連接狀態(tài)時(shí),如果觸發(fā)留言事件,電話的狀態(tài)會(huì)變?yōu)閽鞕C(jī)狀態(tài)(OffHook)當(dāng)電話處于已連接狀態(tài)時(shí),如果觸發(fā)掛起事件,電話的狀態(tài)會(huì)變?yōu)閽炱馉顟B(tài)(OnHold)Fire是觸發(fā)事件的函數(shù),這里觸發(fā)了一個(gè)呼叫事件觸發(fā)呼叫事件之后,電話的狀態(tài)變更為響鈴狀態(tài),所以
Assert.AreEqual(State.Ringing, phoneCall.State)
的斷言是正確的。
Stateless支持的特性
- 對(duì)任何.NET類(lèi)型的狀態(tài)和觸發(fā)器的通用支持
- 分層狀態(tài)
- 狀態(tài)的進(jìn)入和退出事件
- 保護(hù)子句以支持條件轉(zhuǎn)換
- 內(nèi)省
與此同時(shí),還提供一些有用的擴(kuò)展:
- 支持外部的狀態(tài)存儲(chǔ)(例如:由ORM跟蹤屬性)
- 參數(shù)化觸發(fā)器
- 可重入狀態(tài)
- 支持DOT格式圖導(dǎo)出
分層狀態(tài)
在以下例子中,OnHold
狀態(tài)是Connected
狀態(tài)的子狀態(tài)。這意味著電話掛起的時(shí)候,還是連接狀態(tài)的。
phoneCall.Configure(State.OnHold) .SubstateOf(State.Connected) .Permit(Trigger.TakenOffHold, State.Connected) .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);
狀態(tài)的進(jìn)入和退出事件
在前面的例子中,StartCallTimer()
方法會(huì)在通話連接時(shí)執(zhí)行,StopCallTimer()
方法會(huì)在通話結(jié)束時(shí)執(zhí)行(或者電話掛起的時(shí)候,或者把電話被扔到墻上毀壞的時(shí)候.)。
當(dāng)電話的狀態(tài)從已連接(Connected)變?yōu)閽炱?OnHold)時(shí), 不會(huì)觸發(fā)StartCallTimer()
方法和StopCallTimer()
方法, 這是因?yàn)?code>OnHold是Connected
的子狀態(tài)。
外部狀態(tài)存儲(chǔ)
有時(shí)候,當(dāng)前對(duì)象的狀態(tài)需要來(lái)自于一個(gè)ORM對(duì)象,或者需要將當(dāng)前對(duì)象的狀態(tài)保存到一個(gè)ORM對(duì)象中。為了支持這種外部狀態(tài)存儲(chǔ),StateMachine
類(lèi)的構(gòu)造函數(shù)支持了讀寫(xiě)狀態(tài)值。
var stateMachine = new StateMachine<State, Trigger>( () => myState.Value, s => myState.Value = s);
內(nèi)省
狀態(tài)機(jī)可以通過(guò)StateMachine.PermittedTriggers
屬性,提供一個(gè)當(dāng)前對(duì)象狀態(tài)下,可以觸發(fā)的觸發(fā)器列表。并提供了一個(gè)方法StateMachine.GetInfo()
來(lái)獲取有關(guān)狀態(tài)的配置信息。
保護(hù)子句
狀態(tài)機(jī)將根據(jù)保護(hù)子句在多個(gè)轉(zhuǎn)換之間進(jìn)行選擇。
phoneCall.Configure(State.OffHook) .PermitIf(Trigger.CallDialled, State.Ringing, () => IsValidNumber) .PermitIf(Trigger.CallDialled, State.Beeping, () => !IsValidNumber);
注意:
配置中的保護(hù)子句必須是互斥的,子狀態(tài)可以通過(guò)重新指定來(lái)覆蓋狀態(tài)轉(zhuǎn)換,但是子狀態(tài)不能覆蓋父狀態(tài)允許的狀態(tài)轉(zhuǎn)換。
參數(shù)化觸發(fā)器
Stateless中支持將強(qiáng)類(lèi)型參數(shù)指定給觸發(fā)器。
var assignTrigger = stateMachine.SetTriggerParameters<string>(Trigger.Assign); stateMachine.Configure(State.Assigned) .OnEntryFrom(assignTrigger, email => OnAssigned(email)); stateMachine.Fire(assignTrigger, "joe@example.com");
導(dǎo)出DOT圖
Stateless還提供了一個(gè)在運(yùn)行時(shí)生成DOT圖代碼的功能,使用生成的DOT圖代碼,我們可以生成可視化的狀態(tài)機(jī)圖。
這里我們可以使用UmlDotGraph.Format()
方法來(lái)生成DOT圖代碼。
phoneCall.Configure(State.OffHook) .PermitIf(Trigger.CallDialled, State.Ringing, IsValidNumber); string graph = UmlDotGraph.Format(phoneCall.GetInfo());
生成的DOT圖代碼例子
digraph { compound=true; node [shape=Mrecord] rankdir="LR" subgraph clusterOpen { label = "Open" Assigned [label="Assigned|exit / Function"]; } Deferred [label="Deferred|entry / Function"]; Closed [label="Closed"]; Open -> Assigned [style="solid", label="Assign / Function"]; Assigned -> Assigned [style="solid", label="Assign"]; Assigned -> Closed [style="solid", label="Close"]; Assigned -> Deferred [style="solid", label="Defer"]; Deferred -> Assigned [style="solid", label="Assign / Function"]; }
圖形化之后的DOT圖例子
一個(gè)BugTracker的例子
看完了這么多介紹,下面我們來(lái)操練一下, 編寫(xiě)一個(gè)Bug的狀態(tài)機(jī)。
假設(shè)在當(dāng)前的BugTracker系統(tǒng)中,Bug有4個(gè)種狀態(tài)Open, Assigned, Deferred, Closed。由此我們可以創(chuàng)建一個(gè)枚舉類(lèi)State
。
public enum State { Open, Assigned, Deferred, Closed }
如果想改變Bug的狀態(tài),這里有3種動(dòng)作,Assign, Defer, Close。
public enum Trigger { Assign, Defer, Close }
下面我們列舉一下Bug對(duì)象可能的狀態(tài)變化。
- 每個(gè)Bug的初始狀態(tài)是Open
- 如果當(dāng)前Bug的狀態(tài)是Open, 觸發(fā)動(dòng)作Assign, Bug的狀態(tài)會(huì)變?yōu)锳ssigned
- 如果當(dāng)前Bug的狀態(tài)是Assigned, 觸發(fā)動(dòng)作Defer, Bug的狀態(tài)會(huì)變?yōu)镈eferred
- 如果當(dāng)前Bug的狀態(tài)是Assigned, 觸發(fā)動(dòng)作Close, Bug的狀態(tài)會(huì)變?yōu)镃losed
- 如果當(dāng)前Bug的狀態(tài)是Assigned, 觸發(fā)動(dòng)作Assign, Bug的狀態(tài)會(huì)保持Assigned(變更Bug修改者的場(chǎng)景)
如果當(dāng)前Bug的狀態(tài)是Deferred, 觸發(fā)動(dòng)作Assign, Bug的狀態(tài)會(huì)變?yōu)锳ssigned由此我們可以編寫(xiě)B(tài)ug類(lèi)
public class Bug { State _state = State.Open; StateMachine<State, Trigger> _machine; StateMachine<State, Trigger>.TriggerWithParameters<string> _assignTrigger; string _title; string _assignee; public Bug(string title) { _title = title; _machine = new StateMachine<State, Trigger>(() => _state, s => _state = s); _assignTrigger = _machine.SetTriggerParameters<string>(Trigger.Assign); _machine.Configure(State.Open).Permit(Trigger.Assign, State.Assigned); _machine.Configure(State.Assigned) .OnEntryFrom(_assignTrigger, assignee => _assignee = assignee) .SubstateOf(State.Open) .PermitReentry(Trigger.Assign) .Permit(Trigger.Close, State.Closed) .Permit(Trigger.Defer, State.Deferred); _machine.Configure(State.Deferred) .OnEntry(() => _assignee = null) .Permit(Trigger.Assign, State.Assigned); } public string CurrentState { get { return _machine.State.ToString(); } } public string Title { get { return _title; } } public string Assignee { get { if (string.IsNullOrWhiteSpace(_assignee)) { return "Not Assigned"; } return _assignee; } } public void Assign(string assignee) { _machine.Fire(_assignTrigger, assignee); } public void Defer() { _machine.Fire(Trigger.Defer); } public void Close() { _machine.Fire(Trigger.Close); } }
代碼解釋?zhuān)?/p>
- 每個(gè)Bug都應(yīng)該有個(gè)指派人和標(biāo)題,所以這里我添加了一個(gè)Assignee和Title屬性
- 當(dāng)指派Bug時(shí),需要指定一個(gè)指派人,所以Assign動(dòng)作的觸發(fā)器我使用的是一個(gè)參數(shù)化的觸發(fā)器
- 當(dāng)Bug對(duì)象進(jìn)入Assigned狀態(tài)時(shí),我將當(dāng)前指定的指派人賦值給了_
assignee
字段。最終效果
這里我們先展示一個(gè)正常的操作流程。
class Program { static void Main(string[] args) { Bug bug = new Bug("Hello World!"); Console.WriteLine($"Current State: {bug.CurrentState}"); bug.Assign("Lamond Lu"); Console.WriteLine($"Current State: {bug.CurrentState}"); Console.WriteLine($"Current Assignee: {bug.Assignee}"); bug.Defer(); Console.WriteLine($"Current State: {bug.CurrentState}"); Console.WriteLine($"Current Assignee: {bug.Assignee}"); bug.Assign("Lu Nan"); Console.WriteLine($"Current State: {bug.CurrentState}"); Console.WriteLine($"Current Assignee: {bug.Assignee}"); bug.Close(); Console.WriteLine($"Current State: {bug.CurrentState}"); } }
運(yùn)行結(jié)果
下面我們修改代碼,我們?cè)趧?chuàng)建一個(gè)Bug之后,立即嘗試關(guān)閉它
class Program { static void Main(string[] args) { Bug bug = new Bug("Hello World!"); bug.Close(); } }
重新運(yùn)行程序之后,程序會(huì)拋出以下異常。
Unhandled Exception: System.InvalidOperationException: No valid leaving transitions are permitted from state 'Open' for trigger 'Close'. Consider ignoring the trigger.
當(dāng)Bug處于Open狀態(tài)的時(shí)候,觸發(fā)Close動(dòng)作,由于沒(méi)有任何次態(tài)定義,所以拋出了異常,這與我們前面定義的邏輯相符,如果希望程序支持Open -> Closed的狀態(tài)變化,我們需要修改Open狀態(tài)的配置,允許Open狀態(tài)通過(guò)Close動(dòng)作變?yōu)镃losed狀態(tài)。
_machine.Configure(State.Open) .Permit(Trigger.Assign, State.Assigned) .Permit(Trigger.Close, State.Closed);
由此可見(jiàn)我們完全可以根據(jù)自身項(xiàng)目的需求,定義一個(gè)簡(jiǎn)單的工作流,Stateless會(huì)自動(dòng)幫我們驗(yàn)證出錯(cuò)誤的流程操作。
總結(jié)
今天我為大家分享了一下.NET中的狀態(tài)機(jī)庫(kù)Stateless, 使用它我們可以很容易的定義出自己業(yè)務(wù)需要的狀態(tài)機(jī),或者基于狀態(tài)機(jī)的工作流,本文大部分的內(nèi)容都來(lái)自官方Github,有興趣的同學(xué)可以深入研究一下。
到此這篇關(guān)于.NET中的狀態(tài)機(jī)庫(kù)Stateless的操作流程的文章就介紹到這了,更多相關(guān).NET狀態(tài)機(jī)Stateless內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ASP.NET中利用Segments取得URL的文件名的一種方法分享
在ASP.NET中,取得請(qǐng)求頁(yè)的URL地址有多種方式,其中有一種方式取得網(wǎng)頁(yè)文件名。2011-09-09淺談ASP.NET Core中間件實(shí)現(xiàn)分布式 Session
這篇文章主要介紹了淺談ASP.NET Core中間件實(shí)現(xiàn)分布式 Session,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11asp.net水晶報(bào)表參數(shù)字段在代碼中賦值的方法
這篇文章主要介紹了asp.net水晶報(bào)表參數(shù)字段在代碼中賦值的方法,實(shí)例分析了asp.net中水晶報(bào)表的使用技巧,需要的朋友可以參考下2015-05-05VS2015+Qt5+OpenCV3開(kāi)發(fā)環(huán)境配置
這篇文章主要為大家詳細(xì)介紹了VS2015+Qt5+OpenCV3開(kāi)發(fā)環(huán)境配置,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06ASP.NET MVC3的偽靜態(tài)實(shí)現(xiàn)代碼
最近使用asp.net MVC3開(kāi)發(fā)B2C電子商務(wù)系統(tǒng),為了SEO的優(yōu)化工作,需要通過(guò)路由實(shí)現(xiàn)偽靜態(tài)URL,后續(xù)再根據(jù)需要生成真正的靜態(tài)頁(yè)面,不直接走路由訪問(wèn)具體的頁(yè)面2011-12-12asp.net textbox javascript實(shí)現(xiàn)enter與ctrl+enter互換 文本框發(fā)送消息與換行(類(lèi)似
今天與大家分享一下 asp.net textbox javascript實(shí)現(xiàn)enter與ctrl+enter互換 文本框發(fā)送消息與換行(類(lèi)似于QQ),這個(gè)功能到底怎么實(shí)現(xiàn)?首先聲明以下幾點(diǎn)2012-01-01ASP.NET MVC API 接口驗(yàn)證的示例代碼
本篇文章主要介紹了ASP.NET MVC API 接口驗(yàn)證的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10