在實際軟體開發中,尤其是工業軟體,每一款設備都有複雜的狀態以及狀態之間的切換的功能需求,在這種情況下,如何管理狀態以及狀態之間切換,和對應狀態下的功能控制,成為非常重要的一個問題。如果處理不好,那這種繁複的狀態將成為“像麵條一樣”纏繞耦合,一團亂麻,真的就是“剪不斷,理還亂”。那如何解決這個問題呢?今天我們以一篇簡單的小例子,簡述如何通過stateless組件,完成狀態的管理和觸發,僅供學習分享使用,如有不足之處,還請指正。
什麼是狀態模式?
關於狀態模式和狀態機,如下所示:
狀態模式:"允許一個對象在其內部狀態改變時改變它的行為。對象看起來似乎修改了它所屬的類 "。(state pattern: "allow an object to alter its behavior when its internal state changes. the object will appear to change its class ".)。對於這個定義,有點抽象,變通理解一下可以這麼理解:狀態擁有者將變更行為委託給狀態對象,狀態擁有者本身只擁有狀態(當然也可以拋棄狀態對象),狀態對象履行變更職責。
狀態機:"依照指定的狀態流程圖,根據當前執行的動作,將當前狀態按照預定的條件變更到新的狀態 "。狀態機有4個要素,即現態、條件、動作、次態。其中,現態和條件是“因”, 動作和次態是“果”。
- 現態 - 是指當前對象的狀態
- 條件 - 當一個條件滿足時,當前對象會觸發一個動作
- 動作 - 條件滿足之後,執行的動作
- 次態 - 條件滿足之後,當前對象的新狀態。次態是相對現態而言的,次態一旦觸發,就變成了現態
狀態遷移圖:"在uml建模中,常常可見,用來描述一個特定的對象所有可能的狀態,以及由於各種事件的發生而引起的狀態之間的轉移和變化,也是配置狀態機按照何種行徑的前提 "。
什麼是stateless?
stateless是一個輕量級、高性能的狀態機庫,它基於.net standard實現,在.net framework和.net core項目中都可以使用,能夠輕鬆地幫助我們實現狀態轉換的邏輯。
- github:https://github.com/dotnet-state-machine/stateless

實例場景說明
本文主要通過stateless組件實現如下一個狀態的管理和觸發,當前有一個設備軟體,它擁有如下幾個狀態(state):
- idle狀態,表示當前沒有運行任務,處於空閒狀態。
- running狀態,表示當前正在工作,處於運行狀態。
- malfunction狀態,表示當前有異常,處於故障狀態。
- recovery狀態,表示經過維修,從故障中恢復了,處於修復狀態。
它擁有觸發動作(trigger):
- work,表示當前開始工作,它可以由idle狀態進入,也可以從recovery狀態進入,程式會進入running狀態。
- fatal,表示遇到異常,它只可以由running狀態進入,程式會進入malfunction狀態。
- repair,表示修復異常,它只可以由malfunction狀態進入,程式會進入recovery狀態。
- finish,表示完成,它可以由running狀態進入,也可以由recovery狀態進入,程式會進入idle狀態。
上述狀態(state)和觸發動作(trigger)以及之間的流轉,如下圖所示:

創建項目
為了演示狀態變化,我們創建一個winform應用程式“okcoder.stateless.wintool”和一個公共庫“okcoder.stateless.common”,如下所示:

安裝stateless組件
在visual studio 2022開發工具中,可以通過nuget包管理器進行安裝。在解決方案右鍵,打開右鍵菜單,然後點擊“管理解決方案的nuget程式包”在打開的nuget包解決方案,搜索stateless,然後安裝即可。當前最新版本為v5.18.0,如下所示:

定義狀態和觸發器
狀態機管理狀態的變化和觸發事件,所以定義狀態和觸發器,它們是枚舉類型,根據實例場景說明進行定義toolstate如下所示:
namespace Okcoder.Stateless.Common
{
/// <summary>
/// 工具状态
/// </summary>
public enum ToolState
{
Idle=0, //空闲状态
Running=1, //运行状态
Malfunction = 2, // 故障状态
Recovery=3, //已恢复状态
}
}
tooltrigger定義如下所示:
namespace Okcoder.Stateless.Common
{
public enum ToolTrigger
{
Work = 0, // 开始运转
Fatal = 1, // 出异常
Finish = 2, // 完成
Repair=3, //维修
}
}
定義接口
定義itoolstateservice接口,它表示狀態機所具備的能力,如觸發動作,狀態判斷,狀態機導出dotgraph等,如下所示:
namespace Okcoder.Stateless.Common
{
/// <summary>
/// 状态机接口
/// </summary>
public interface IToolStateService
{
/// <summary>
/// 初始化状态
/// </summary>
void Init();
/// <summary>
/// 开始工作
/// </summary>
/// <returns></returns>
ToolResult Work();
/// <summary>
/// 遇到严重错误
/// </summary>
/// <returns></returns>
ToolResult Fatal();
/// <summary>
/// 维修
/// </summary>
/// <returns></returns>
ToolResult Repair();
/// <summary>
/// 完成
/// </summary>
/// <returns></returns>
ToolResult Finish();
/// <summary>
/// 是否是Idle状态
/// </summary>
/// <returns></returns>
bool IsIdleState();
/// <summary>
/// 是否运行状态
/// </summary>
/// <returns></returns>
bool IsRunningState();
/// <summary>
/// 是否故障状态
/// </summary>
/// <returns></returns>
bool IsMalfunctionState();
/// <summary>
/// 是否恢复状态
/// </summary>
/// <returns></returns>
bool IsRecoveryState();
/// <summary>
/// 获取当前状态
/// </summary>
/// <returns></returns>
ToolState GetToolState();
/// <summary>
/// 状态机导出DotGraph
/// </summary>
/// <param name="path"></param>
void ExportDotGraph(string path);
}
}
定義設備工作itoolworkservice接口,它表示設備運行工作,執行操作。如下所示:
namespace Okcoder.Stateless.Common
{
/// <summary>
/// 设备工作流程接口
/// </summary>
public interface IToolWorkService
{
/// <summary>
/// 是否制造故障
/// </summary>
bool IsMakeFatal { get; set; }
/// <summary>
/// 开始工作
/// </summary>
void DoWork();
/// <summary>
/// 维修
/// </summary>
void Repair();
}
}
實現服務
實現狀態機服務toolstateservice,它主要應用stateless組件實現狀態的管理和觸發動作切換狀態,如下所示:
using Stateless;
using Stateless.Graph;
using System;
using System.IO;
using System.Text;
namespace Okcoder.Stateless.Common
{
/// <summary>
/// 状态机服务
/// </summary>
public class ToolStateService : IToolStateService
{
private Action<ToolState> toolAction;// 动作
private StateMachine<ToolState, ToolTrigger> toolStateMachine;
public ToolStateService(Action<ToolState> toolAction)
{
//定义状态机
this.toolStateMachine = new StateMachine<ToolState, ToolTrigger>(ToolState.Idle);
//配置每一个状态所允许的动作
this.toolStateMachine.Configure(ToolState.Idle).Permit(ToolTrigger.Work, ToolState.Running);
this.toolStateMachine.Configure(ToolState.Running).Permit(ToolTrigger.Fatal, ToolState.Malfunction).Permit(ToolTrigger.Finish, ToolState.Idle);
this.toolStateMachine.Configure(ToolState.Malfunction).Permit(ToolTrigger.Repair, ToolState.Recovery);
this.toolStateMachine.Configure(ToolState.Recovery).Permit(ToolTrigger.Work, ToolState.Running).Permit(ToolTrigger.Finish, ToolState.Idle);
this.toolAction = toolAction;
}
/// <summary>
/// 初始化状态
/// </summary>
public void Init()
{
this.toolAction(ToolState.Idle);
}
/// <summary>
/// 响应触发动作
/// </summary>
/// <param name="trigger"></param>
private ToolResult Fire(ToolTrigger trigger)
{
ToolResult toolResult = new ToolResult();
try
{
if (!this.toolStateMachine.CanFire(trigger))
{
toolResult.IsOk = false;
toolResult.Desc = $"当前状态是{this.toolStateMachine.State},不允许执行{trigger}操作";
}
else
{
this.toolStateMachine.Fire(trigger);
toolResult.IsOk = true;
toolResult.Desc = "Ok";
DoAction();
}
}
catch (InvalidOperationException ex)
{
toolResult.IsOk = false;
toolResult.Desc = ex.Message;
}
return toolResult;
}
/// <summary>
/// 判断是否在状态
/// </summary>
/// <param name="toolState"></param>
/// <returns></returns>
private bool IsInState(ToolState toolState)
{
return this.toolStateMachine.IsInState(toolState);
}
/// <summary>
/// 事件通知
/// </summary>
private void DoAction()
{
if (this.toolAction != null)
{
this.toolAction(this.toolStateMachine.State);
}
}
/// <summary>
/// 开始工作
/// </summary>
public ToolResult Work()
{
return this.Fire(ToolTrigger.Work);
}
/// <summary>
/// 遇到严重错误
/// </summary>
public ToolResult Fatal()
{
return this.Fire(ToolTrigger.Fatal);
}
/// <summary>
/// 维修
/// </summary>
public ToolResult Repair()
{
return this.Fire(ToolTrigger.Repair);
}
/// <summary>
/// 完成
/// </summary>
public ToolResult Finish()
{
return this.Fire(ToolTrigger.Finish);
}
/// <summary>
/// 是否是Idle状态
/// </summary>
/// <returns></returns>
public bool IsIdleState()
{
return this.IsInState(ToolState.Idle);
}
/// <summary>
/// 是否运行状态
/// </summary>
/// <returns></returns>
public bool IsRunningState()
{
return this.IsInState(ToolState.Running);
}
/// <summary>
/// 是否故障状态
/// </summary>
/// <returns></returns>
public bool IsMalfunctionState()
{
return this.IsInState(ToolState.Malfunction);
}
/// <summary>
/// 是否恢复状态
/// </summary>
/// <returns></returns>
public bool IsRecoveryState()
{
return this.IsInState(ToolState.Recovery);
}
public ToolState GetToolState()
{
return this.toolStateMachine.State;
}
/// <summary>
/// 状态机导出DotGraph
/// </summary>
/// <param name="path"></param>
public void ExportDotGraph(string path)
{
var info = this.toolStateMachine.GetInfo();
string graph = UmlDotGraph.Format(info);
using (FileStream stream = File.OpenWrite(path))
{
using (StreamWriter writer = new StreamWriter(stream))
{
writer.Write(graph);
writer.Close();
}
stream.Close();
}
}
}
}
其中statemachine是狀態機的核心類,它有兩個泛型參數,分別表示toolstate和tooltrigger,構造函數中傳入初始狀態,在本示例中,傳入toolstage.idle即可。
statemachine通過對象實例的configure方法配置允許的狀態及通過permit方法配置當前狀態允許通過trigger轉換的新的狀態。
配置完成後,通過fire方法響應觸發動作,通過isinstate方法判斷當前是否處於指定的狀態。
在状态机服务类构造函数中,接收一个Action<string>的委托方法,用于输出状态到UI层,当然也可以采用其他事件订阅发布的解耦方法。
其中toolresult返回值,表示狀態機執行後返回的接口,它是一個類,如下所示:
namespace Okcoder.Stateless.Common
{
/// <summary>
/// 工具执行结果
/// </summary>
public class ToolResult
{
public bool IsOk { get; set; }
public string Desc { get; set; }
}
}
實現工作服務toolworkservice,主要是模擬設備工具運行,當開始工作時,輸出文本到ui頁面上,如果有異常,則停止;若無異常,則運行完成,如下所示:
namespace Okcoder.Stateless.Common
{
/// <summary>
/// 设备工作服务
/// </summary>
public class ToolWorkService:IToolWorkService
{
private Action<string> _progress;
private IToolStateService _toolStateService;
private bool _isMakeFatal=false;
public bool IsMakeFatal
{
get { return _isMakeFatal; }
set { _isMakeFatal = value; }
}
public ToolWorkService(IToolStateService toolStateService, Action<string> progress,bool isMakeFatal=false)
{
_progress = progress;
_toolStateService = toolStateService;
_isMakeFatal = isMakeFatal;
}
public void DoWork()
{
BeginWork();
Work();
Finish();
}
public void Repair()
{
var result = this._toolStateService.Repair();
if (result.IsOk)
{
OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 故障被修好了");
}
else
{
OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 故障没有被修好");
}
}
/// <summary>
/// 开始工作
/// </summary>
private void BeginWork()
{
var result = this._toolStateService.Work();
if (result.IsOk)
{
OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 开始工作了");
}
else
{
OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 开始工作失败了");
}
}
/// <summary>
/// 工作过程
/// </summary>
private void Work()
{
for (int i = 0; i < 30; i++)
{
if (_isMakeFatal)
{
if (i > 10)
{
MarkFatal();// 运行一段时间,报故障
break;
}
}
OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 当前正在工作 - {i}");
Thread.Sleep(300);
}
}
/// <summary>
/// 完成工作
/// </summary>
private void Finish()
{
var result = this._toolStateService.Finish();
if (result.IsOk)
{
OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 完成工作了");
}
else
{
OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {result.Desc}");
}
}
private void MarkFatal()
{
var result = this._toolStateService.Fatal();
if (result.IsOk)
{
OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 哎呀,出错了呢?");
}
else
{
OutPutInfo($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 制造故障失败");
}
}
/// <summary>
/// 输出信息
/// </summary>
/// <param name="msg"></param>
private void OutPutInfo(string msg)
{
if (_progress != null)
{
_progress(msg);
}
}
}
}
其中构造函数接收一个Action<string>的委托,用于向UI输出信息,当然也可以是其他的形式。
ui調用
在okcoder.stateless.wintool項目中,創建frmmain頁面,在構造函數中定義itoolstateservice和itoolworkservice的對象實例,並且在load方法中初始化。
當用戶點擊work時,開始調用itoolworkservice的work方法,在執行過程中,會輸出信息到ui頁面。
在ui頁面有一個複選框,用於設置是否觸發異常,可以模擬有異常的場景,如果出現異常,則點擊repair按鈕進行修復。
在執行各個動作時,可以看到不同的狀態變化。如下所示:
using Okcoder.Stateless.Common;
namespace Okcoder.Stateless.WinTool
{
public partial class FrmMain : Form
{
private IToolStateService toolStateService;
private IToolWorkService toolWorkService;
public FrmMain()
{
InitializeComponent();
this.toolStateService = new ToolStateService(ToolStateAction);
this.toolWorkService = new ToolWorkService(toolStateService, ToolWorkProcessAction, this.chkFatal.Checked);
}
private void FrmMain_Load(object sender, EventArgs e)
{
this.toolStateService.Init();
}
/// <summary>
/// 开始工作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnWork_Click(object sender, EventArgs e)
{
this.txtInfo.Text = string.Empty;
this.toolWorkService.IsMakeFatal = this.chkFatal.Checked;
Task.Run(() =>
{
this.toolWorkService.DoWork();
});
}
/// <summary>
/// 维修
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnRepair_Click(object sender, EventArgs e)
{
this.toolWorkService.Repair();
}
/// <summary>
/// 导出状态机DotGraph文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnExport_Click(object sender, EventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.DefaultExt = "txt";
saveFileDialog.Filter = "txt文件|*.txt";
saveFileDialog.FileName = "Okcoder.txt";
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
var filePath = saveFileDialog.FileName;
this.toolStateService.ExportDotGraph(filePath);
MessageBox.Show("导出成功");
}
}
private void ToolStateAction(ToolState toolState)
{
this.Invoke(() =>
{
this.lblState.Text = toolState.ToString();
switch (toolState)
{
case ToolState.Idle:
case ToolState.Recovery:
this.btnWork.Enabled = true;
this.btnRepair.Enabled = false;
this.lblState.ForeColor = Color.Black;
break;
case ToolState.Running:
this.btnWork.Enabled = false;
this.btnRepair.Enabled = false;
this.lblState.ForeColor = Color.Goldenrod;
break;
case ToolState.Malfunction:
this.btnWork.Enabled = false;
this.btnRepair.Enabled = true;
this.lblState.ForeColor = Color.Red;
break;
}
this.Refresh();
});
}
private void ToolWorkProcessAction(string msg)
{
this.Invoke(() =>
{
this.txtInfo.AppendText(msg + "\r\n");
});
}
}
}
實例演示
經過上述步驟,運行程式,然後點擊work按鈕,默認無故障時如下所示:

當勾選故障,模擬出現故障時,如下所示:

以上就是《推薦一款高性能狀態機管理解決方案》的全部內容,關於更多詳細內容,可參考官方文檔。希望能夠一起學習,共同進步。
學習編程,從關注【老碼識途】開始,為大家分享更多文章!!!