.NET中的狀態(tài)機庫Stateless的操作流程
介紹
什么是狀態(tài)機和狀態(tài)模式

狀態(tài)機是一種用來進行對象建模的工具,它是一個有向圖形,由一組節(jié)點和一組相應的轉移函數(shù)組成。狀態(tài)機通過響應一系列事件而“運行”。每個事件都在屬于“當前” 節(jié)點的轉移函數(shù)的控制范圍內(nèi),其中函數(shù)的范圍是節(jié)點的一個子集。函數(shù)返回“下一個”(也許是同一個)節(jié)點。這些節(jié)點中至少有一個必須是終態(tài)。當?shù)竭_終態(tài), 狀態(tài)機停止。
狀態(tài)模式主要用來解決對象狀態(tài)轉換比較復雜的情況。它把狀態(tài)的邏輯判斷轉移到不同的類中,可以把復雜的邏輯簡單化。
狀態(tài)機的要素
狀態(tài)機有4個要素,即現(xiàn)態(tài)、條件、動作、次態(tài)。其中,現(xiàn)態(tài)和條件是“因”, 動作和次態(tài)是“果”。
- 現(xiàn)態(tài) - 是指當前對象的狀態(tài)
- 條件 - 當一個條件滿足時,當前對象會觸發(fā)一個動作
- 動作 - 條件滿足之后,執(zhí)行的動作
- 次態(tài) - 條件滿足之后,當前對象的新狀態(tài)。次態(tài)是相對現(xiàn)態(tài)而言的,次態(tài)一旦觸發(fā),就變成了現(xiàn)態(tài)
Stateless
Stateless是一款基于.NET的開源狀態(tài)機庫,最新版本4.2.1, 使用它你可以很輕松的在.NET中創(chuàng)建狀態(tài)機和以狀態(tài)機為基礎的輕量級工作流。
由于整個項目基于.NET Standard的編寫的,所以在.NET Framework和.NET Core項目中都可以使用。
項目源代碼 https://github.com/dotnet-state-machine/stateless
以下是一個使用Stateless編寫的打電話流程
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);
代碼解釋
當前初始化了一個狀態(tài)機來描述點電話的狀態(tài),這里電話的初始狀態(tài)為掛機狀態(tài)(OffHook)當電話處于掛機狀態(tài)時,如果觸發(fā)被呼叫事件,電話的狀態(tài)會變?yōu)轫戔彔顟B(tài)(Ringing)當電話處于響鈴狀態(tài)時,如果觸發(fā)通過連接事件,電話的狀態(tài)會變?yōu)橐堰B接狀態(tài)(Connected)當電話處于已連接狀態(tài)時,系統(tǒng)會開始計時,已連接狀態(tài)變?yōu)槠渌麪顟B(tài)時,系統(tǒng)會結束計時當電話處于已連接狀態(tài)時,如果觸發(fā)留言事件,電話的狀態(tài)會變?yōu)閽鞕C狀態(tài)(OffHook)當電話處于已連接狀態(tài)時,如果觸發(fā)掛起事件,電話的狀態(tài)會變?yōu)閽炱馉顟B(tài)(OnHold)Fire是觸發(fā)事件的函數(shù),這里觸發(fā)了一個呼叫事件觸發(fā)呼叫事件之后,電話的狀態(tài)變更為響鈴狀態(tài),所以
Assert.AreEqual(State.Ringing, phoneCall.State)的斷言是正確的。
Stateless支持的特性
- 對任何.NET類型的狀態(tài)和觸發(fā)器的通用支持
- 分層狀態(tài)
- 狀態(tài)的進入和退出事件
- 保護子句以支持條件轉換
- 內(nèi)省
與此同時,還提供一些有用的擴展:
- 支持外部的狀態(tài)存儲(例如:由ORM跟蹤屬性)
- 參數(shù)化觸發(fā)器
- 可重入狀態(tài)
- 支持DOT格式圖導出
分層狀態(tài)
在以下例子中,OnHold狀態(tài)是Connected狀態(tài)的子狀態(tài)。這意味著電話掛起的時候,還是連接狀態(tài)的。
phoneCall.Configure(State.OnHold)
.SubstateOf(State.Connected)
.Permit(Trigger.TakenOffHold, State.Connected)
.Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);
狀態(tài)的進入和退出事件
在前面的例子中,StartCallTimer()方法會在通話連接時執(zhí)行,StopCallTimer()方法會在通話結束時執(zhí)行(或者電話掛起的時候,或者把電話被扔到墻上毀壞的時候.)。
當電話的狀態(tài)從已連接(Connected)變?yōu)閽炱?OnHold)時, 不會觸發(fā)StartCallTimer()方法和StopCallTimer()方法, 這是因為OnHold是Connected的子狀態(tài)。
外部狀態(tài)存儲
有時候,當前對象的狀態(tài)需要來自于一個ORM對象,或者需要將當前對象的狀態(tài)保存到一個ORM對象中。為了支持這種外部狀態(tài)存儲,StateMachine類的構造函數(shù)支持了讀寫狀態(tài)值。
var stateMachine = new StateMachine<State, Trigger>(
() => myState.Value,
s => myState.Value = s);
內(nèi)省
狀態(tài)機可以通過StateMachine.PermittedTriggers屬性,提供一個當前對象狀態(tài)下,可以觸發(fā)的觸發(fā)器列表。并提供了一個方法StateMachine.GetInfo()來獲取有關狀態(tài)的配置信息。
保護子句
狀態(tài)機將根據(jù)保護子句在多個轉換之間進行選擇。
phoneCall.Configure(State.OffHook)
.PermitIf(Trigger.CallDialled, State.Ringing, () => IsValidNumber)
.PermitIf(Trigger.CallDialled, State.Beeping, () => !IsValidNumber);
注意:
配置中的保護子句必須是互斥的,子狀態(tài)可以通過重新指定來覆蓋狀態(tài)轉換,但是子狀態(tài)不能覆蓋父狀態(tài)允許的狀態(tài)轉換。
參數(shù)化觸發(fā)器
Stateless中支持將強類型參數(shù)指定給觸發(fā)器。
var assignTrigger = stateMachine.SetTriggerParameters<string>(Trigger.Assign);
stateMachine.Configure(State.Assigned)
.OnEntryFrom(assignTrigger, email => OnAssigned(email));
stateMachine.Fire(assignTrigger, "joe@example.com");
導出DOT圖
Stateless還提供了一個在運行時生成DOT圖代碼的功能,使用生成的DOT圖代碼,我們可以生成可視化的狀態(tài)機圖。
這里我們可以使用UmlDotGraph.Format()方法來生成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圖例子

一個BugTracker的例子
看完了這么多介紹,下面我們來操練一下, 編寫一個Bug的狀態(tài)機。
假設在當前的BugTracker系統(tǒng)中,Bug有4個種狀態(tài)Open, Assigned, Deferred, Closed。由此我們可以創(chuàng)建一個枚舉類State。
public enum State
{
Open,
Assigned,
Deferred,
Closed
}
如果想改變Bug的狀態(tài),這里有3種動作,Assign, Defer, Close。
public enum Trigger
{
Assign,
Defer,
Close
}
下面我們列舉一下Bug對象可能的狀態(tài)變化。
- 每個Bug的初始狀態(tài)是Open
- 如果當前Bug的狀態(tài)是Open, 觸發(fā)動作Assign, Bug的狀態(tài)會變?yōu)锳ssigned
- 如果當前Bug的狀態(tài)是Assigned, 觸發(fā)動作Defer, Bug的狀態(tài)會變?yōu)镈eferred
- 如果當前Bug的狀態(tài)是Assigned, 觸發(fā)動作Close, Bug的狀態(tài)會變?yōu)镃losed
- 如果當前Bug的狀態(tài)是Assigned, 觸發(fā)動作Assign, Bug的狀態(tài)會保持Assigned(變更Bug修改者的場景)
如果當前Bug的狀態(tài)是Deferred, 觸發(fā)動作Assign, Bug的狀態(tài)會變?yōu)锳ssigned由此我們可以編寫B(tài)ug類
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);
}
}
代碼解釋:
- 每個Bug都應該有個指派人和標題,所以這里我添加了一個Assignee和Title屬性
- 當指派Bug時,需要指定一個指派人,所以Assign動作的觸發(fā)器我使用的是一個參數(shù)化的觸發(fā)器
- 當Bug對象進入Assigned狀態(tài)時,我將當前指定的指派人賦值給了_
assignee字段。最終效果
這里我們先展示一個正常的操作流程。
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}");
}
}
運行結果

下面我們修改代碼,我們在創(chuàng)建一個Bug之后,立即嘗試關閉它
class Program
{
static void Main(string[] args)
{
Bug bug = new Bug("Hello World!");
bug.Close();
}
}
重新運行程序之后,程序會拋出以下異常。
Unhandled Exception: System.InvalidOperationException: No valid leaving transitions are permitted from state 'Open' for trigger 'Close'. Consider ignoring the trigger.
當Bug處于Open狀態(tài)的時候,觸發(fā)Close動作,由于沒有任何次態(tài)定義,所以拋出了異常,這與我們前面定義的邏輯相符,如果希望程序支持Open -> Closed的狀態(tài)變化,我們需要修改Open狀態(tài)的配置,允許Open狀態(tài)通過Close動作變?yōu)镃losed狀態(tài)。
_machine.Configure(State.Open) .Permit(Trigger.Assign, State.Assigned) .Permit(Trigger.Close, State.Closed);
由此可見我們完全可以根據(jù)自身項目的需求,定義一個簡單的工作流,Stateless會自動幫我們驗證出錯誤的流程操作。
總結
今天我為大家分享了一下.NET中的狀態(tài)機庫Stateless, 使用它我們可以很容易的定義出自己業(yè)務需要的狀態(tài)機,或者基于狀態(tài)機的工作流,本文大部分的內(nèi)容都來自官方Github,有興趣的同學可以深入研究一下。
到此這篇關于.NET中的狀態(tài)機庫Stateless的操作流程的文章就介紹到這了,更多相關.NET狀態(tài)機Stateless內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
asp.net網(wǎng)站首頁根據(jù)IP自動跳轉指定頁面的示例
本文介紹的程序主要實現(xiàn)根據(jù)IP地址或地址段或IP所在城市進行自動跳轉到指定頁面的功能,需要的朋友可以參考下2014-02-02
.NET 6.0 + WPF 使用 Prism 框架實現(xiàn)導航
Prism是一款專為XAML應用程序設計的開源框架,主要面向WPF和Xamarin Forms等平臺,文章介紹了Prism的基本概念、安裝步驟和使用方法,是開發(fā)企業(yè)級應用程序的首選框架,感興趣的朋友跟隨小編一起看看吧2024-09-09

