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.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的功能差不多了解完毕了,开始将状态机融入到项目中实际使用起来,也已经加入到日程中。

Stateless是一个基于C#创建状态机的简单库的更多相关文章

  1. 公布一个基于 Reactor 模式的 C++ 网络库

    公布一个基于 Reactor 模式的 C++ 网络库 陈硕 (giantchen_AT_gmail) Blog.csdn.net/Solstice 2010 Aug 30 本文主要介绍 muduo 网 ...

  2. 基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍。最后我们将会实现一个基于Server-Sent Event和Flask简单的在线聊天室。

    基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍.最后我们将会实现一个基于S ...

  3. RSuite 一个基于 React.js 的 Web 组件库

    RSuite http://rsuite.github.io RSuite 是一个基于 React.js 开发的 Web 组件库,参考 Bootstrap 设计,提供其中常用组件,支持响应式布局. 我 ...

  4. AgileRepository - 一个基于接口的Repository快速开发库

    AgileRepository 这是一个可以帮助你快速开发Repository的lib.有点像SpringData JPA根据方法名.注解来自动生成查询方法的功能. 对于一些简单的查询,只需要定义接口 ...

  5. CXF 入门:创建一个基于WS-Security标准的安全验证(CXF回调函数使用,)

    http://jyao.iteye.com/blog/1346547 注意:以下客户端调用代码中获取服务端ws实例,都是通过CXF 入门: 远程接口调用方式实现 直入正题! 以下是服务端配置 ==== ...

  6. JAVA WEB快速入门之从编写一个基于SpringBoot+Mybatis快速创建的REST API项目了解SpringBoot、SpringMVC REST API、Mybatis等相关知识

    JAVA WEB快速入门系列之前的相关文章如下:(文章全部本人[梦在旅途原创],文中内容可能部份图片.代码参照网上资源) 第一篇:JAVA WEB快速入门之环境搭建 第二篇:JAVA WEB快速入门之 ...

  7. 在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)

    本文首发于:码友网--一个专注.NET/.NET Core开发的编程爱好者社区. 文章目录 C#/.NET基于Topshelf创建Windows服务的系列文章目录: C#/.NET基于Topshelf ...

  8. 如何创建一个基于 .NET Core 3 的 WPF 项目

    在 Connect(); 2018 大会上,微软发布了 .NET Core 3 Preview,以及基于 .NET Core 3 的 WPF:同时还发布了 Visual Studio 2019 预览版 ...

  9. 如何创建一个基于 MSBuild Task 的跨平台的 NuGet 工具包

    MSBuild 的 Task 为我们扩展项目的编译过程提供了强大的扩展性,它使得我们可以用 C# 语言编写扩展:利用这种扩展性,我们可以为我们的项目定制一部分的编译细节.NuGet 为我们提供了一种自 ...

随机推荐

  1. PAT 1024 科学记数法

    PAT 1024 科学记数法 科学计数法是科学家用来表示很大或很小的数字的一种方便的方法,其满足正则表达式 [+-][1-9].[0-9]+E[+-][0-9]+,即数字的整数部分只有 1 位,小数部 ...

  2. 监控微信小程序中的慢HTTP请求

    摘要: 请求时间太长,影响用户体验,使用 Fundebug 监控慢请求. Fundebug 的微信小程序监控插件在 0.5.0 版本已经支持监控 HTTP 请求错误,在小程序中通过wx.request ...

  3. 前端常见的HTML+CSS面试题(附答案)

    HTML 1. <image>标签上title属性与alt属性的区别是什么? alt属性是为了给那些不能看到你文档中图像的浏览者提供文字说明的.且长度必须少于100个英文字符或者用户必须保 ...

  4. iOS开发工具:Alcatraz、SVGKit、Lin以及Transformifier等

    转自:http://www.cocoachina.com/applenews/devnews/2013/0606/6352.html Alcatraz:Xcode包管理器 Alcatraz是一个开源的 ...

  5. WorkFlow一:WorkFlow基础配置

    1.使用事物代码SWU3进入WF配置页. 2.展开第一个运行环境维护文件夹,选中第一个配置RFC目标,点击生成.完成后可点击运行按钮测试是否成功. 同上,挨个激活. 3.激活第二个文件夹‘维护环境定义 ...

  6. 七月伊始 LeetCode算法总结

    七月伊始 早上买了LeetCode的课程,解锁了付费题目,付费倒逼学习: 意识到这么久学习的东西,都是写在自己的笔记, 如今希望自己能够用自己拙笔记录这个学习和总结的过程. 队列的学习 设计循环队列 ...

  7. swagger页面详细讲解

  8. logger(二)logback简介及其实现原理

    一.logback简介 logback是log4j创始人写的,性能比log4j要好,目前主要分为3个模块 logback-core:核心代码模块 logback-classic:log4j的一个改良版 ...

  9. MySQL数据物理备份之tar打包备份

    复制数据文件方式,可以使用cp或tar 1.停止服务 [root@localhost mysql]# systemctl stop mysqld [root@localhost mysql]# net ...

  10. 04、rpm+yum+tar解压

    Linux 下安装软件: 1.rpm 软件包的安装 一般安装都用 rpm -ivh 包路径及名字 如:rpm -ivh /soft/RealPlayer11GOLD.rpm   --安装/soft下 ...