很多业务系统开发中,不可避免的会出现状态变化,通常采用的情形可能是使用工作流去完成,但是对于简单场景下,用工作流有点大财小用感觉,比如订单业务中,订单状态的变更,涉及到的状态量不是很多,即使通过简单的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.orgviz.jsdot command line工具。

  诸如生成的字符串在viz.js中解析的状态机图形。

  

10、异步触发

  该状态机支持异步操作,对于Entry/Exit方法等都有相应的异步方法,带Async结尾,并且对于触发也有异步方法FireAsync(),需要注意的是,尽管使用了异步,但仍然是单线程操作,不能被多个线程同时使用。

stateMachine.Configure(State.Assigned)
.OnEntryAsync(async () => await SendEmailToAssignee()); await stateMachine.FireAsync(Trigger.Assigned);

 至此,对于状态机Stateless的功能差不多了解完毕了,开始将状态机融入到项目中实际使用起来,也已经加入到日程中。

2019-09-22,望技术有成后能回来看见自己的脚步

.Net轻量状态机Stateless的更多相关文章

  1. .Net轻量状态机Stateless的简单应用

    对于大部分系统中流程的变更,是十分正常的事情,小到一个状态的切换,大到整个系统都是围绕业务流再走,复杂点的有工作流引擎,简单点的几个if/else收工,但是往往有那种,心有余而力不足的,比简单复杂,比 ...

  2. Raspkate - 基于.NET的可运行于树莓派的轻量型Web服务器

    最近在业余时间玩玩树莓派,刚开始的时候在树莓派里写一些基于wiringPi库的C语言程序来控制树莓派的GPIO引脚,从而控制LED发光二极管的闪烁,后来觉得,是不是可以使用HTML5+jQuery等流 ...

  3. 一种简单,轻量,灵活的C#对象转Json对象的方案

    简单,是因为只有一个类 轻量,是因为整个类代码只有300行 灵活,是因为扩展方式只需要继承重写某个方法即可 补充:修正无法处理可空值类型的bug 首先我将这个类称之为JsonBuilder,我希望它以 ...

  4. 一种简单,轻量,灵活的C#对象转Json对象的方案(续)

    本文参考资料 一种简单,轻量,灵活的C#对象转Json对象的方案 [源码]Literacy 快速反射读写对象属性,字段 一段废话 之前我已经介绍了这个方案的名称为JsonBuilder,这套方案最大的 ...

  5. Dapper.NET——轻量ORM

    Dapper.NET使用 http://www.cnblogs.com/yankliu-vip/p/4182892.html 本文目录 Dapper.NET使用 1.为什么选择Dapper 2.以Da ...

  6. 编写轻量ajax组件01-对比webform平台上的各种实现方式

    前言 Asp.net WebForm 和 Asp.net MVC(简称MVC) 都是基于Asp.net的web开发框架,两者有很大的区别,其中一个就是MVC更加注重http本质,而WebForm试图屏 ...

  7. Vue.js:轻量高效的前端组件化方案

    转发一篇尤老师对vue.js的介绍,了解vue.js的来龙去脉.不过现在已经是2.0了,也有添加一些新的东西,当然有些东西也改了. Vue.js:轻量高效的前端组件化方案 Vue.js 是我在2014 ...

  8. 基于netty轻量的高性能分布式RPC服务框架forest<下篇>

    基于netty轻量的高性能分布式RPC服务框架forest<上篇> 文章已经简单介绍了forest的快速入门,本文旨在介绍forest用户指南. 基本介绍 Forest是一套基于java开 ...

  9. 基于netty轻量的高性能分布式RPC服务框架forest<上篇>

    工作几年,用过不不少RPC框架,也算是读过一些RPC源码.之前也撸过几次RPC框架,但是不断的被自己否定,最近终于又撸了一个,希望能够不断迭代出自己喜欢的样子. 顺便也记录一下撸RPC的过程,一来作为 ...

随机推荐

  1. Golang 解决 Iris 被墙的依赖包

    使用 Golang 的 Iris web 框架时,用 go get github.com/kataras/iris 命令久久无法下载,最后还报一堆错误. 使用  GOPROXY 可解决问题,也可参考如 ...

  2. 纯数据结构Java实现(0/11)(开篇)

    为嘛要写 本来按照我的风格,其实很不喜欢去写这些细节的东西,因为笔记上直接带过了. 本来按照我的风格,如果要写,那也是直接上来就干,根本不解释这些大纲,参考依据. 本来按照我的风格,不想太显山露水,但 ...

  3. C# ModBus 读取数据

    简单介绍: 项目上需要与多家公司做接口对接.我们提供接口的有,其他公司提供的接口也有.所有的接口全部对接完了,遇到一个非常棘手的问题,需要获取甲方船厂设备上的状态,就给了一个文档,文档上写了IP.端口 ...

  4. Integer 使用==判断127和超过128的数据的区别

    Integer封装类型字数据当超过一定长度后,若使用==来判断数否相等,那么判断的结果是false; Integer的范围是超过128就是false. 对于所有封装类而言,建议使用equals来进行判 ...

  5. PythonI/O进阶学习笔记_2.魔法函数

    前言: 本文一切观点和测试代码是在python3的基础上. Content: 1.什么是魔法函数,魔法函数__getitem__在python中应用. 2.python的数据模型和数据模型这种设计对p ...

  6. HDU 5135(再思考)

    题意略. 思路:再思考后发现,为了构造出最大的三角形面积和,我们应该尽量让长的棍子相组合,这样构造出的三角形面积和最大,贪心能解. #include<bits/stdc++.h> usin ...

  7. MySql定时器,亲测可用

    1. 查看数据库的event功能是否开启,在MySql中event默认是关闭的,需要查看并且要确保event处于开启状态 sql:show VARIABLES LIKE '%sche%'; 如果eve ...

  8. AVL自平衡二叉树

    详细的具体步骤 : 一篇讲的很好博客 AVL,红黑树优先博客-Never 先对二叉树的不平衡结构进行总结: 各种旋转 特别注意字母含义(结构)和其旋转操作之间的区别 二叉树不平衡结构 性质 平衡操作 ...

  9. 【selenium】-自动化测试的前提

    本文由小编根据慕课网视频亲自整理,转载请注明出处和作者. 1.为什么要做自动化? 2.是否适合做自动化? 时间:时间如果很紧,连做功能测试的时间都很紧张,是没有时间做自动化的. 人员:如果都是初级的测 ...

  10. UVALive 7264 Kejin Game 网络流+最小割

    Kejin Game 题意:一个人有一颗技能树, 现在它想修练到某个技能 (假设为x), 现在修一个技能有3种方式: 1, 将该技能的前置技能都学完了,才能学该技能. 2, 取消一个技能 与 另一个技 ...