.NET中的状态机库Stateless

标题:.NET中的状态机库Stateless 

作者:Lamond Lu 

地址:https://www.cnblogs.com/lwqlun/p/10674018.html[1]

介绍

什么是状态机和状态模式

640?wx_fmt=jpeg

状态机是一种用来进行对象建模的工具,它是一个有向图形,由一组节点和一组相应的转移函数组成。状态机通过响应一系列事件而“运行”。每个事件都在属于“当前” 节点的转移函数的控制范围内,其中函数的范围是节点的一个子集。函数返回“下一个”(也许是同一个)节点。这些节点中至少有一个必须是终态。当到达终态, 状态机停止。

状态模式主要用来解决对象状态转换比较复杂的情况。它把状态的逻辑判断转移到不同的类中,可以把复杂的逻辑简单化。

状态机的要素

状态机有4个要素,即现态、条件、动作、次态。其中,现态和条件是“因”, 动作和次态是“果”。

•现态 - 是指当前对象的状态•条件 - 当一个条件满足时,当前对象会触发一个动作•动作 - 条件满足之后,执行的动作•次态 - 条件满足之后,当前对象的新状态。次态是相对现态而言的,次态一旦触发,就变成了现态

Stateless

Stateless是一款基于.NET的开源状态机库,最新版本4.2.1, 使用它你可以很轻松的在.NET中创建状态机和以状态机为基础的轻量级工作流。

由于整个项目基于.NET Standard的编写的,所以在.NET Framework和.NET Core项目中都可以使用。

项目源代码 https://github.com/dotnet-state-machine/stateless[2]

以下是一个使用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()方法, 这是因为OnHoldConnected的子状态。

外部状态存储

有时候,当前对象的状态需要来自于一个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图例子

640?wx_fmt=png

一个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}"); } }

运行结果

640?wx_fmt=png

下面我们修改代码,我们在创建一个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,有兴趣的同学可以深入研究一下。

References

[1]https://www.cnblogs.com/lwqlun/p/10674018.html
[2]https://github.com/dotnet-state-machine/stateless


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316380.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.net core webapi 前后端开发分离后的配置和部署

背景&#xff1a;现在越来越多的企业都采用了在开发上前后端分离&#xff0c;前后端开发上的分离有很多种&#xff0c;那么今天&#xff0c;我来分享一下项目中得的前后端分离。B/S Saas 项目&#xff1a;&#xff08;这个项目可以理解成个人中心&#xff0c;当然不止这么点功…

ASP.NET Core使用Jaeger实现分布式追踪

前言最近我们公司的部分.NET Core的项目接入了Jaeger&#xff0c;也算是稍微完善了一下.NET团队的技术栈。至于为什么选择Jaeger而不是Skywalking&#xff0c;这个问题我只能回答&#xff0c;大佬们说了算。前段时间也在CSharpCorner写过一篇类似的介绍Exploring Distributed T…

长沙开发者技术大会暨.NET技术社区成立大会倒数第13天

待你扬帆起航&#xff0c;一起精彩纷呈&#xff01;长沙开发者技术大会暨.NET技术社区成立大会倒数第13天&#xff01;2019年4月21日期待与你相聚在.NET技术社区&#xff01;我们今天会完成海报制作和报表表单&#xff0c;海报内容初步如下所示&#xff1a;活动信息 长沙开发者…

C#并行编程(1):理解并行

什么是并行并行是指两个或者多个事件在同一时刻发生。在程序运行中&#xff0c;并行指多个CPU核心同时执行不同的任务&#xff1b;对于单核心CPU,严格来说是没有程序并行的。并行是为了提高任务执行效率&#xff0c;更快的获取结果。与并发的区别&#xff1a;并发是指两个或者多…

P2163 [SHOI2007]园丁的烦恼(二维数点模板题)

P2163 [SHOI2007]园丁的烦恼 题意&#xff1a; 在一个二维平面内有一些点&#xff0c;给你一个左上角和右下角的点&#xff0c;问这个范围内有多少点 题解&#xff1a; 二维数点模板题 我们设F(a,b)表示以(0,0)为左下角&#xff0c;(a,b)为右上角的矩阵内有多少点 如图不难…

Orleans MultiClient 多个Silo复合客户端

介绍Orleans.MultiClient 是一个 Orleans 复合客户端&#xff0c;只需要简单配置就可以简单高效连接和请求 Orleans 服务。Orleans.MultiClient 可以轻松连接多个不同服务的 Orleans 服务,在请求 Orleans 时会根据请求的接口自动寻找 Orleans 客户端&#xff0c;使用者无需关心…

ASP.NET Core 进程内(InProcess)托管(6)《从零开始学ASP.NET CORE MVC》:

本文出自《从零开始学ASP.NET CORE MVC》推荐文章&#xff1a;ASP.NET Core 中的 Main方法ASP.NET Core 进程内(InProcess)托管在这个视频中我们将讨论在ASP.NET Core中的进程内(InProcess)托管模型什么是Kestrel服务器当一个 ASP.NET Core 应用程序执行的时候&#xff0c;.NET…

约会安排 HDU - 4553

约会安排 HDU - 4553 题意&#xff1a; 题意又丑又长就不叙述了 题解&#xff1a; 这个题一开始理解错了。。。题目相当于是有三种情况占据时间&#xff0c;分别是学习&#xff0c;女神和屌丝&#xff0c;我们用不同的lazy来表示女神和屌丝&#xff0c;根据优先级去更新状态…

ML.NET机器学习、API容器化与Azure DevOps实践(一):简介

打算使用几篇文章介绍一下.NET下的机器学习框架ML.NET的具体应用&#xff0c;包括一些常用的业务场景、算法的选择、模型的训练以及RESTful API的创建、机器学习服务容器化&#xff0c;以及基于Azure DevOps的容器化部署等等相关的内容。如果你从来没有玩过机器学习&#xff0c…

Picture POJ - 1177(矩形周长并))

Picture POJ - 1177 题目&#xff1a; 多个矩阵相交在一起&#xff0c;问新图形的周长是多少 题解&#xff1a; 参考题解 周长分为两部分&#xff1a;横线和竖线 横线计算方法&#xff1a;现在总区间被覆盖的长度和上一次总区间被覆盖的长度之差的绝对值 那么我们只需要从…

聊一聊C# 8.0中的await foreach

很开心今天能与大家一起聊聊C# 8.0中的新特性-Async Streams,一般人通常看到这个词表情是这样.简单说,其实就是C# 8.0中支持await foreach.或者说,C# 8.0中支持异步返回枚举类型async Task<IEnumerable<T>>.好吧,还不懂?Good,这篇文章就是为你写的,看完这篇文章,你…

ASP.NET Core 实现带认证功能的Web代理服务器

引言最近在公司开发了一个项目&#xff0c;项目部署架构图如下&#xff1a;思路如图中文本所述&#xff0c;公司大数据集群不允许直接访问外网&#xff0c;需要一个网关服务器代理请求&#xff0c;本处服务器A就是边缘代理服务器的作用。通常技术人员最快捷的思路是在服务器A上…

Unfair contest(个人做法)

Unfair contest 题意&#xff1a; 两个人参赛&#xff0c;n个评委打分&#xff0c;去掉s个最高分&#xff0c;去掉t个最低分&#xff0c;剩下分求平均分&#xff0c;平均分大的获胜。你是第n个评委&#xff0c;此时已知前n-1个评委所打分数&#xff0c;现在轮到你打分&#x…

ASP.NET Core 进程外(out-of-process)托管(7)《从零开始学ASP.NET CORE MVC》

本文出自《从零开始学ASP.NET CORE MVC》推荐文章&#xff1a;ASP.NET Core 进程内(InProcess)托管ASP.NET Core 进程内(InProcess)托管我们先简单回顾下 ASP.NET Core 中,要配置InProcess的服务器&#xff0c;需要在项目文件中添加< AspNetCoreHostingModel >元素&#…

eShopOnContainers 知多少[10]:部署到 K8S | AKS

1. 引言断断续续&#xff0c;感觉这个系列又要半途而废了。趁着假期&#xff0c;赶紧再更一篇&#xff0c;介绍下如何将eShopOnContainers部署到K8S上&#xff0c;进而实现大家常说的微服务上云。2. 先了解下 Helm读过我上篇文章ASP.NET Core 借助 K8S 玩转容器编排的同学&…

DI是实现面向切面和面向抽象的前提

DI越来越重要DI就是依赖注入&#xff0c;现在来说&#xff0c;大部分框架都是以DI为基础组件的&#xff0c;每一个框架都有自己的DI组件&#xff0c;像dotnet core&#xff0c;java spring等&#xff0c;也都为自己的框架量身打造了DI工具。面向对象的几个原则依赖倒置原则&…

.net core 并发下的线程安全问题

抱歉&#xff0c;其实内容并不如题&#xff01;&#xff01;&#xff01;背景&#xff08;写测试demo所出现的异常&#xff0c;供大家学习与拍砖&#xff09;&#xff1a;.net core webapi项目&#xff0c;做了一个授权的filter&#xff08;真正的生产项目的话&#xff0c;JWT很…

cf1555B. Two Tables

cf1555B. Two Tables 题意&#xff1a; 一个大矩阵空间内放置一个矩阵a&#xff0c;现在要再往这个空间内放一个矩阵b&#xff0c;a移动距离len才能放下b&#xff0c;问len最小是多少 题解&#xff1a; 不难发现左右或上下移动是最佳的&#xff0c;斜着移动是最不好的。此时…

cf1555C Coin Rows

cf1555C Coin Rows 题意&#xff1a; 有一个两行m列的地图&#xff0c;每个格子都有对应的价值&#xff0c;有a&#xff0c;b两个人&#xff0c;都从左上角到右下角&#xff0c;且都只能向右向下走&#xff0c;a先出发&#xff0c;a每到一个格子&#xff0c;就会获得这个地方…

C#并行编程(2):.NET线程池

线程 Thread在总结线程池之前&#xff0c;先来看一下.NET线程。.NET线程与操作系统(Windows)线程有什么区别&#xff1f;.NET利用Windows的线程处理功能。在C#程序编写中&#xff0c;我们首先会新建一个线程对象System.Threading.Thread&#xff0c;并为其指定一个回调方法&…