網站首頁 編程語言 正文
介紹
什么是狀態機和狀態模式
狀態機是一種用來進行對象建模的工具,它是一個有向圖形,由一組節點和一組相應的轉移函數組成。狀態機通過響應一系列事件而“運行”。每個事件都在屬于“當前” 節點的轉移函數的控制范圍內,其中函數的范圍是節點的一個子集。函數返回“下一個”(也許是同一個)節點。這些節點中至少有一個必須是終態。當到達終態, 狀態機停止。
狀態模式主要用來解決對象狀態轉換比較復雜的情況。它把狀態的邏輯判斷轉移到不同的類中,可以把復雜的邏輯簡單化。
狀態機的要素
狀態機有4個要素,即現態、條件、動作、次態。其中,現態和條件是“因”, 動作和次態是“果”。
- 現態 - 是指當前對象的狀態
- 條件 - 當一個條件滿足時,當前對象會觸發一個動作
- 動作 - 條件滿足之后,執行的動作
- 次態 - 條件滿足之后,當前對象的新狀態。次態是相對現態而言的,次態一旦觸發,就變成了現態
Stateless
Stateless
是一款基于.NET的開源狀態機庫,最新版本4.2.1, 使用它你可以很輕松的在.NET中創建狀態機和以狀態機為基礎的輕量級工作流。
由于整個項目基于.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);
代碼解釋
當前初始化了一個狀態機來描述點電話的狀態,這里電話的初始狀態為掛機狀態(OffHook)當電話處于掛機狀態時,如果觸發被呼叫事件,電話的狀態會變為響鈴狀態(Ringing)當電話處于響鈴狀態時,如果觸發通過連接事件,電話的狀態會變為已連接狀態(Connected)當電話處于已連接狀態時,系統會開始計時,已連接狀態變為其他狀態時,系統會結束計時當電話處于已連接狀態時,如果觸發留言事件,電話的狀態會變為掛機狀態(OffHook)當電話處于已連接狀態時,如果觸發掛起事件,電話的狀態會變為掛起狀態(OnHold)Fire是觸發事件的函數,這里觸發了一個呼叫事件觸發呼叫事件之后,電話的狀態變更為響鈴狀態,所以
Assert.AreEqual(State.Ringing, phoneCall.State)
的斷言是正確的。
Stateless支持的特性
- 對任何.NET類型的狀態和觸發器的通用支持
- 分層狀態
- 狀態的進入和退出事件
- 保護子句以支持條件轉換
- 內省
與此同時,還提供一些有用的擴展:
- 支持外部的狀態存儲(例如:由ORM跟蹤屬性)
- 參數化觸發器
- 可重入狀態
- 支持DOT格式圖導出
分層狀態
在以下例子中,OnHold
狀態是Connected
狀態的子狀態。這意味著電話掛起的時候,還是連接狀態的。
phoneCall.Configure(State.OnHold) .SubstateOf(State.Connected) .Permit(Trigger.TakenOffHold, State.Connected) .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);
狀態的進入和退出事件
在前面的例子中,StartCallTimer()
方法會在通話連接時執行,StopCallTimer()
方法會在通話結束時執行(或者電話掛起的時候,或者把電話被扔到墻上毀壞的時候.)。
當電話的狀態從已連接(Connected)變為掛起(OnHold)時, 不會觸發StartCallTimer()
方法和StopCallTimer()
方法, 這是因為OnHold
是Connected
的子狀態。
外部狀態存儲
有時候,當前對象的狀態需要來自于一個ORM對象,或者需要將當前對象的狀態保存到一個ORM對象中。為了支持這種外部狀態存儲,StateMachine
類的構造函數支持了讀寫狀態值。
var stateMachine = new StateMachine<State, Trigger>( () => myState.Value, s => myState.Value = s);
內省
狀態機可以通過StateMachine.PermittedTriggers
屬性,提供一個當前對象狀態下,可以觸發的觸發器列表。并提供了一個方法StateMachine.GetInfo()
來獲取有關狀態的配置信息。
保護子句
狀態機將根據保護子句在多個轉換之間進行選擇。
phoneCall.Configure(State.OffHook) .PermitIf(Trigger.CallDialled, State.Ringing, () => IsValidNumber) .PermitIf(Trigger.CallDialled, State.Beeping, () => !IsValidNumber);
注意:
配置中的保護子句必須是互斥的,子狀態可以通過重新指定來覆蓋狀態轉換,但是子狀態不能覆蓋父狀態允許的狀態轉換。
參數化觸發器
Stateless中支持將強類型參數指定給觸發器。
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圖代碼,我們可以生成可視化的狀態機圖。
這里我們可以使用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的狀態機。
假設在當前的BugTracker系統中,Bug有4個種狀態Open, Assigned, Deferred, Closed。由此我們可以創建一個枚舉類State
。
public enum State { Open, Assigned, Deferred, Closed }
如果想改變Bug的狀態,這里有3種動作,Assign, Defer, Close。
public enum Trigger { Assign, Defer, Close }
下面我們列舉一下Bug對象可能的狀態變化。
- 每個Bug的初始狀態是Open
- 如果當前Bug的狀態是Open, 觸發動作Assign, Bug的狀態會變為Assigned
- 如果當前Bug的狀態是Assigned, 觸發動作Defer, Bug的狀態會變為Deferred
- 如果當前Bug的狀態是Assigned, 觸發動作Close, Bug的狀態會變為Closed
- 如果當前Bug的狀態是Assigned, 觸發動作Assign, Bug的狀態會保持Assigned(變更Bug修改者的場景)
如果當前Bug的狀態是Deferred, 觸發動作Assign, Bug的狀態會變為Assigned由此我們可以編寫Bug類
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動作的觸發器我使用的是一個參數化的觸發器
- 當Bug對象進入Assigned狀態時,我將當前指定的指派人賦值給了_
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}"); } }
運行結果
下面我們修改代碼,我們在創建一個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狀態的時候,觸發Close動作,由于沒有任何次態定義,所以拋出了異常,這與我們前面定義的邏輯相符,如果希望程序支持Open -> Closed的狀態變化,我們需要修改Open狀態的配置,允許Open狀態通過Close動作變為Closed狀態。
_machine.Configure(State.Open) .Permit(Trigger.Assign, State.Assigned) .Permit(Trigger.Close, State.Closed);
由此可見我們完全可以根據自身項目的需求,定義一個簡單的工作流,Stateless會自動幫我們驗證出錯誤的流程操作。
總結
今天我為大家分享了一下.NET中的狀態機庫Stateless, 使用它我們可以很容易的定義出自己業務需要的狀態機,或者基于狀態機的工作流,本文大部分的內容都來自官方Github,有興趣的同學可以深入研究一下。
原文鏈接:https://www.cnblogs.com/lwqlun/p/10674018.html
相關推薦
- 2022-04-21 Android自定義View實現標簽流效果_Android
- 2022-07-22 Math.ceil() 函數使用介紹
- 2023-07-02 Python中星號的五種用法小結_python
- 2022-09-10 python讀取文件列表并排序的實現示例_python
- 2022-10-18 AJAX請求以及解決跨域問題詳解_AJAX相關
- 2023-12-02 windows7右下角的隱藏欄不見了
- 2022-07-07 在python中使用[[v]*n]*n遇到的坑及解決_python
- 2022-07-11 docker容器時區不對問題
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支