.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, "[email protected]");

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,望技术有成后能回来看见自己的脚步

原文地址:https://www.cnblogs.com/xcj26/p/11572220.html

时间: 2024-08-30 12:04:11

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

轻量的Memcached代理Twemproxy的部署

轻量的Memcached代理Twemproxy的部署 Twemproxy(又称为nutcracker)是一个轻量级的Redis和Memcached代理,主要用来减少对后端缓存服务器的连接数.由Twitter开源出来的缓存服务器集群管理工具,主要用来弥补Redis和Memcached对集群(cluster)管理指出的不足. Twemproxy是一个快速的单线程代理程序,支持Memcached ASCII协议和更新的Redis协议. Twemproxy最了不起的地方就在于它能在节点失败的时候卸载它,

轻量型ORM框架Dapper的使用

在真实的项目开发中,可能有些人比较喜欢写SQL语句,但是对于EF这种ORM框架比较排斥,那么轻量型的Dapper就是一个不错的选择,即让你写sql语句了,有进行了关系对象映射.其实对于EF吧,我说下我个人的见解,EF是相对来说比较重的ORM框架,它是讲究通用性的框架,而非单纯的实现某种需求的框架,不得不否认的说,EF的开发效率要比用单纯的ADO.NET写sql语句的效率高的不是一点半点,可能很多人会说EF的性能低等等,针对这种问题有时候是需要折中的来讲,从另一个方面来讲,你用了ADO.NET写s

推荐轻量友好的.NET测试断言工具Shoudly

Shoudly是一个轻量的断言(Assertion)框架,用于补充.NET框架下的测试工具.Shoudly将焦点放在当断言失败时如何简单精准的给出很好的错误信息. Shouldly在GitHub的开源地址:https://github.com/shouldly/shouldly Shouldly的官方文档:http://docs.shouldly-lib.net/ 为什么要Shoudly? 我们知道通常测试代码中一个断言是这样写的: Assert.That(contestant.Points,

一个轻量系统的诞生

经过几个月的业余时间,为一家公司设计了如下一套简单,轻量的内部管理系统.在这几个月中,基本牺牲了所有的业余时间,不乏晚上加班加点.一个人和工厂讨论需求,提出设计想法,和工厂人员确定这么“整”还是那么“整”,是否可满足具体要求. 然后再回到家进行Coding.... 做该项目的意义何在?其实市场上有很多XX ERP,XXX协同系统.这些系统功能可能很强大,强大到太强而不能用. 最重要的一点是,并不是适合具体公司的业务流程和业务内容. 如果你们公司买了一套系统有可能就是将你公司的管理调整到和要上的系

OWIN轻量型框架介绍

OWIN轻量型框架介绍 阅读目录 引言 框架的特色 如何启动 各项功能 静态路由的3种写法 伪静态路由的支持 处理Form表单提交的文件 流式处理Post请求的数据 多种请求类型自动识别 响应处理 请求响应上下文 自定义默认处理函数 内置各种便捷函数 复合类型的请求处理 框架的扩展 静态内容的支持 跨域Post的支持 基础类型继承灵活处理 尾声 回到顶部 引言 什么是OWIN,我就不介绍了,请自行搜索,这里主要是介绍自行开发的OWIN框架的特点和用法.由于.NET的web框架都比较庞大,导致性能

Bourbon – 简单、轻量的 Sass 混入库

Bourbon 是一个简单易用的 Sass 混入(Mixin)库,无需配置.该混入包含用于支持所有现代浏览器的 CSS3 属性前缀.前缀需要确保在旧的浏览器支持优雅降级.Bourbon 使用 SCSS 语法. 您可能感兴趣的相关文章 真是好东西!13种非常动感的页面加载动画效果 你见过吗?9款超炫的复选框(Checkbox)效果 超赞!基于 Bootstrap 的响应式的后台管理模板 太赞了!超炫的页面切换动画效果[附源码下载] 创意无限!一组网页边栏过渡动画[附源码下载] 立即下载      

比Newtonsoft.Json轻量快速简洁的实体JSON转换库YeaJur.Mapper

在使用MVC的时候,我们经常用到Newtonsoft.Json来进行实体和JSON 之间的转换,但是有时候,有些实体Newtonsoft.Json转换会出现异常.YeaJur.Mapper正是为了解决这些问题而来,并比Newtonsoft.Json轻量,转换速度快,使用简洁,测试结果如下 PK项 YeaJur.Mapper Newtonsoft.Json 版本 1.0 9.0.1 大小 6KB 514KB 实例(json格式) [ { "Products": [ { "Id&

基于netty轻量的高性能分布式RPC服务框架forest&lt;下篇&gt;

基于netty轻量的高性能分布式RPC服务框架forest<上篇> 文章已经简单介绍了forest的快速入门,本文旨在介绍forest用户指南. 基本介绍 Forest是一套基于java开发的RPC框架,除了常规的点对点调用外,Motan还提供服务治理功能,包括服务节点的自动发现.摘除.高可用和负载均衡等. 架构概述 Forest中分为服务提供方(RPC Server),服务调用方(RPC Client)和服务注册中心(Registry)三个角色. Server提供服务,向Registry注册

Android轻量缓存框架--ASimpleCache

[转] 大神真面目 稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识.前端.后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过! 编辑推荐: 做Android应用开发的同学们相信对“缓存”这个词不陌生,缓存可能有多方面的概念,这里大概列举下程序开发的缓存大概有哪些: 1.服务端控制缓存 如volley请求库,便是通过服务端的“Cache-Control”和“max-age”来告诉客户端有没有缓存以及缓存的时间,也是推荐的使用