Stateless是一个基于C#创建状态机的简单库
Stateless是一个基于C#创建状态机的简单库
.Net轻量状态机Stateless
很多业务系统开发中,不可避免的会出现状态变化,通常采用的情形可能是使用工作流去完成,但是对于简单场景下,用工作流有点大财小用感觉,比如订单业务中,订单状态的变更,涉及到的状态量不是很多,即使通过简单的if-else也能足够使用,甚至是用上switch去减少if-else的使用,都是可以的,尽管这会丧失某些东西。为更好的优化整个流程,此时会考虑到使用状态模式来解决一些问题。
Stateless状态机GitHub:https://github.com/dotnet-state-machine/stateless
一、状态模式与状态机
1、状态模式:"允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类 "。 (State Pattern: "Allow an object to alter its behavior when its internal state changes. The object will appear to change its class ".)
对于这个定义,有点抽象,变通理解一下可以这么理解:状态拥有者将变更行为委托给状态对象,状态拥有者本身只拥有状态(当然也可以抛弃状态对象),状态对象履行变更职责。
2、状态机:"依照指定的状态流程图,根据当前执行的动作,将当前状态按照预定的条件变更到新的状态 "。
状态机有4个要素,即现态、条件、动作、次态。其中,现态和条件是“因”, 动作和次态是“果”。
- 现态 - 是指当前对象的状态
- 条件 - 当一个条件满足时,当前对象会触发一个动作
- 动作 - 条件满足之后,执行的动作
- 次态 - 条件满足之后,当前对象的新状态。次态是相对现态而言的,次态一旦触发,就变成了现态
3、状态迁移图:"在UML建模中,常常可见,用来描述一个特定的对象所有可能的状态,以及由于各种事件的发生而引起的状态之间的转移和变化,也是配置状态机按照何种行径的前提 "。
二、Stateless功能介绍
Stateless是一个基于C#创建状态机的简单库。基于.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.Stat

1、功能特性
状态机常见功能:
- 支持所有.Net类型的状态和触发器(数字、字符串、枚举等等)
- 分层状态
- 状态的进入和退出事件
- 用卫语句来支持条件转换
- 内省
提供了一些有用的扩展:
- 支持外部的状态存储(例如:由ORM跟踪属性)
- 参数化触发器
- 可重入状态
- 导出DOT格式图
2、分层状态
在以下例子中,OnHold状态是Connected状态的子状态。这意味着电话挂起的时候,还是连接状态的,通过IsInState()方法,可以判定是否当前状态处于父状态下的子状态,比如IsInState(State.Connected)能够返回true,说明当前OnHold状态是处于Connected状态的。
- phoneCall.Configure(State.OnHold)
- .SubstateOf(State.Connected)
- .Permit(Trigger.TakenOffHold, State.Connected)
- .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);
3、状态的进入和退出事件
在前面的例子中,StartCallTimer()方法会在通话连接时执行,StopCallTimer()方法会在通话结束时执行,对应的便是,进入该状态与脱离该状态时候执行的事件。当电话的状态从已连接(Connected)变为挂起(OnHold)时, 不会触发StartCallTimer()方法和StopCallTimer()方法, 这是因为OnHold是Connected的子状态,对于进入和退出事件的处理者,可以传参提供触发动作,现状和次状信息。
4、外部状态存储
有时候,当前对象的状态需要来自于一个ORM对象,或者需要将当前对象的状态保存到一个ORM对象中,UI框架需要存储一个状态到绑定属性中。为了支持这种外部状态存储,StateMachine类的构造函数支持了读写状态值。如代码里,通过使用myState可以去存储和获取状态值。
- var stateMachine = new StateMachine<State, Trigger>(
- () => myState.Value,
- s => myState.Value = s
- );
5、内省
该状态机可以通过StateMachine.PermittedTriggers属性获取当前状态下可以触发的触发器列表。并能够使用StateMachine.GetInfo()获取状态相关的配置信息。

- public IEnumerable<TTrigger> PermittedTriggers
- {
- get
- {
- return GetPermittedTriggers();
- }
- }
- //返回StateMachineInfo对象,包含状态及触发器列表。
- _machine.GetInfo();

6、卫语句
状态机将根据卫语句在多条转换线路之间进行选择,卫语句必须是互斥的,多个卫语句不能同时生效。子状态可以通过重新指定来覆盖状态转换,但是子状态不能覆盖父状态允许的状态转换,当触发器触发时,卫语句开始评估线路选择,因此不会带来其它方面的影响。
- phoneCall.Configure(State.OffHook)
- .PermitIf(Trigger.CallDialled, State.Ringing, () => IsValidNumber)
- .PermitIf(Trigger.CallDialled, State.Beeping, () => !IsValidNumber)
7、参数化触发器
支持将强类型参数提供给触发器,使用方法PermitDynamic()配置状态机时,能够通过触发器参数动态选择目标状态。
- var assignTrigger = stateMachine.SetTriggerParameters<string>(Trigger.Assign);
- stateMachine.Configure(State.Assigned)
- .OnEntryFrom(assignTrigger, email => OnAssigned(email));
- stateMachine.Fire(assignTrigger, "joe@example.com");
8、忽视转换和重入状态
如果触发了一个没有配置过的线路,将会抛出一个异常,通过使用Ignore方法,忽视一些触发,当触发了此类触发器时,不会抛出异常,而改为忽略该次触发。
- phoneCall.Configure(State.Connected)
- .Ignore(Trigger.CallDialled);
另外,一个状态能够使用PermitReentry方法配置为重复进入(从本状态到本状态),entry和exit事件也会被再次触发。
- stateMachine.Configure(State.Assigned)
- .PermitReentry(Trigger.Assigned)
- .OnEntry(() => SendEmailToAssignee());
默认情形下,必须明确忽略哪些触发器。 当未配置的触发器被触发时默认是抛出异常,可以通过使用OnUnhandledTrigger配置状态机覆写处理异常情形。
- stateMachine.OnUnhandledTrigger((state, trigger) => { });
9、导出DOT格式图
运行状态可视化状态机是很有用处的,使用状态机时,代码是命令式的,而状态图是副产物。
- phoneCall.Configure(State.OffHook)
- .PermitIf(Trigger.CallDialled, State.Ringing, IsValidNumber);
- string graph = UmlDotGraph.Format(phoneCall.GetInfo());
UmlDotGraph.Format()方法返回代表状态机的字符串,使用DOT graph语言格式。这个可以被支持DOT graph语言的工具渲染。像graphviz.org和viz.js的dot command line工具。
诸如生成的字符串在viz.js中解析的状态机图形。
10、异步触发
该状态机支持异步操作,对于Entry/Exit方法等都有相应的异步方法,带Async结尾,并且对于触发也有异步方法FireAsync(),需要注意的是,尽管使用了异步,但仍然是单线程操作,不能被多个线程同时使用。
- stateMachine.Configure(State.Assigned)
- .OnEntryAsync(async () => await SendEmailToAssignee());
- await stateMachine.FireAsync(Trigger.Assigned);
至此,对于状态机Stateless的功能差不多了解完毕了,开始将状态机融入到项目中实际使用起来,也已经加入到日程中。
Stateless是一个基于C#创建状态机的简单库的更多相关文章
- 公布一个基于 Reactor 模式的 C++ 网络库
公布一个基于 Reactor 模式的 C++ 网络库 陈硕 (giantchen_AT_gmail) Blog.csdn.net/Solstice 2010 Aug 30 本文主要介绍 muduo 网 ...
- 基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍。最后我们将会实现一个基于Server-Sent Event和Flask简单的在线聊天室。
基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍.最后我们将会实现一个基于S ...
- RSuite 一个基于 React.js 的 Web 组件库
RSuite http://rsuite.github.io RSuite 是一个基于 React.js 开发的 Web 组件库,参考 Bootstrap 设计,提供其中常用组件,支持响应式布局. 我 ...
- AgileRepository - 一个基于接口的Repository快速开发库
AgileRepository 这是一个可以帮助你快速开发Repository的lib.有点像SpringData JPA根据方法名.注解来自动生成查询方法的功能. 对于一些简单的查询,只需要定义接口 ...
- CXF 入门:创建一个基于WS-Security标准的安全验证(CXF回调函数使用,)
http://jyao.iteye.com/blog/1346547 注意:以下客户端调用代码中获取服务端ws实例,都是通过CXF 入门: 远程接口调用方式实现 直入正题! 以下是服务端配置 ==== ...
- JAVA WEB快速入门之从编写一个基于SpringBoot+Mybatis快速创建的REST API项目了解SpringBoot、SpringMVC REST API、Mybatis等相关知识
JAVA WEB快速入门系列之前的相关文章如下:(文章全部本人[梦在旅途原创],文中内容可能部份图片.代码参照网上资源) 第一篇:JAVA WEB快速入门之环境搭建 第二篇:JAVA WEB快速入门之 ...
- 在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)
本文首发于:码友网--一个专注.NET/.NET Core开发的编程爱好者社区. 文章目录 C#/.NET基于Topshelf创建Windows服务的系列文章目录: C#/.NET基于Topshelf ...
- 如何创建一个基于 .NET Core 3 的 WPF 项目
在 Connect(); 2018 大会上,微软发布了 .NET Core 3 Preview,以及基于 .NET Core 3 的 WPF:同时还发布了 Visual Studio 2019 预览版 ...
- 如何创建一个基于 MSBuild Task 的跨平台的 NuGet 工具包
MSBuild 的 Task 为我们扩展项目的编译过程提供了强大的扩展性,它使得我们可以用 C# 语言编写扩展:利用这种扩展性,我们可以为我们的项目定制一部分的编译细节.NuGet 为我们提供了一种自 ...
随机推荐
- css画三角形原理解析
<div id="div1"></div><div id="div2"></div><div id=&qu ...
- 不要在 MySQL 中使用“utf8”,请使用“utf8mb4”
不要在 MySQL 中使用“utf8”,请使用“utf8mb4” 最近我遇到了一个bug,我试着通过Rails在以“utf8”编码的MariaDB中保存一个UTF-8字符串,然后出现了一个离奇的错误: ...
- 彻底抛弃 jQuery ,不然还留着过年?
我以前很喜欢 jQuery,而且说实话,我是先学jQuery,再学 JavaScript 的.所以我写这篇文章有点像是在背叛 jQuery. 我知道,关于为什么不应该用 jQuery 的文章已经汗牛充 ...
- ES6 数组的拓展(五)
一.扩展运算符(...)将数组转化为以,分割的字符串eg: console.log(...[1,2,3,4]); //1 2 3 4 将字符串转化为数组eg: console.log([...'hel ...
- 实验吧——因缺思汀的绕过(sql with rollup)
题目地址:http://ctf5.shiyanbar.com/web/pcat/index.php 通读源码,得知出flag的条件 1.需要post提交uname以及pwd,否则直接die了 if ( ...
- proxychains4配置使用
一丶安装 sudo apt-get install proxychains4 二丶修改配置文件 sudo vim /etc/proxychains.conf 在文本最后加上你的代理服务器地址,如果有用 ...
- SPring boot jpa 封装查询条件
最近使用spring data jpa做了两个项目,对于动态查询的不友好做了个类似hibernate的封装,记录也分享下 首先定义一个所有条件的容器,继承Specification /** * 定义一 ...
- jvm运行时数据区之程序计数器
什么是程序计数器? 程序计数器是一块 较小 的内存空间,它可以看做是当前线程所执行的字节码的 行号指示器 :在虚拟机的概念模型里(仅仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解 ...
- ORM之EF初识
之前有写过ef的codefirst,今天来更进一步认识EF! 一:EF的初步认识 ORM(Object Relational Mapping):对象关系映射,其实就是一种对数据访问的封装.主要实现流程 ...
- ubuntu16.04 共享文件夹之后 /mnt/hgfs目录下没有显示共享的文件夹
root权限执行: apt-get install open-vm-tools vmhgfs-fuse .host:/ /mnt/hgfs