状态机 Stateless VS StateMachine

状态机应用实践
本文介绍了状态机的概念及其在复杂业务场景中的应用。通过对比两款C#状态机库——Stateless和StateMachine,阐述了它们的特点及使用场景。使用状态机能简化代码逻辑,减少状态判断,使代码更加清晰。

【什么是状态机】

一句话概括:

状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。

来解释什么是“状态”( State )。现实事物是有不同状态的,例如一个自动门,就有 open 和 closed 两种状态。我们通常所说的状态机是有限状态机,也就是被描述的事物的状态的数量是有限个,例如自动门的状态就是两个 open 和 closed 。

状态机,也就是 State Machine ,不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。例如,根据自动门的运行规则,我们可以抽象出下面这么一个图。

自动门有两个状态,open 和 closed ,closed 状态下,如果读取开门信号,那么状态就会切换为 open 。open 状态下如果读取关门信号,状态就会切换为 closed 。

状态机的全称是有限状态自动机,自动两个字也是包含重要含义的。给定一个状态机,同时给定它的当前状态以及输入,那么输出状态时可以明确的运算出来的。例如对于自动门,给定初始状态 closed ,给定输入“开门”,那么下一个状态时可以运算出来的。

这样状态机的基本定义我们就介绍完毕了。重复一下:状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。

以上内容引用于:https://zhuanlan.zhihu.com/p/47434856,里面有更详细的状态机描述,大家可以移步详细了解

我们为什么使用状态机,我的项目为独立场景的桌面应用程序,包含10种以下个子功能,菜单要明显的显示出当前状态,并且存在功能之间的组合使用等复杂情况,没有状态机的情况下,我们需要在每个功能的开启处添加各种判断,判断当前状态,判断是否能切换到下一个状态,切换时是否要停止当前状态,还需要全局变量标识当前的状态。这时想起了状态机这个东东,但是没有在C#里面使用过,所以又是一段时间的找资料,具体对比使用了下面两款状态机,之后详细的介绍下两者的用法和缺点。使用状态机后,所有的状态变更统一由状态机控制,代码清晰并避免了大量的判断。

【Stateless】

git地址

https://github.com/dotnet-state-machine/stateless

简介:轻量级的C#状态机,优点可判断状态之间的层级关机、可添加多层父类关系,缺点是多层父类关系造成了其关系混乱,状态触发时很难准确的进入到你期望的状态中,没有选择它的原因就是这个。

用法可参照此篇文章,介绍的很详细,此处不再赘述

https://www.cnblogs.com/CKExp/p/11564882.html

【StateMachine】全名Appccelerate.StateMachine

git地址

https://github.com/appccelerate/statemachine

简介:较完善的状态机,优点是状态可定制化强,可定义状态进入的初始子状态,定义状态重复进入时的规则,定义事件触发时的细节判断,可用性强,缺点是无法判断状态之间的关系,状态的层级关系单一,无法扩展。另外的缺点就是中文资料基本没有,Stateless还有两三篇文章可以学习,这款状态机就只能自己参照git的相关资料,动手一步步测试了。我们最终选择的就是这款状态机,业务较为复杂的时候,推荐使用。

抱歉,今天就写到这里,StateMachine的用法之后添加,先立个小目标,14号前写完这篇文章

<think>我们正在讨论的是在无状态(stateless)环境下动态创建状态机的问题。根据引用[1]和[2],Stateless框架是一个轻量级的状态机框架,它允许我们以简单的方式定义状态机。但是,用户的问题是在无状态环境下动态创建状态机,这意味着我们可能需要在每次请求时重新创建状态机,而不依赖于之前的状态。 引用[2]中展示了一个使用Stateless框架创建状态机的例子,它通过配置状态和触发器的转换关系来定义状态机。但是,这个例子是在代码中静态配置的。如果我们想要动态创建状态机,我们需要在运行时根据某些条件来配置状态机。 引用[4]提到了一个方法:`PermitDynamic`,它允许我们动态选择下一个状态。这提供了一种动态性的方式,但还不够,因为用户需要的是整个状态机的动态创建,而不仅仅是动态选择下一个状态。 因此,我们需要考虑如何在运行时动态地定义状态机的状态和转换规则。我们可以将状态机的配置信息(如状态、触发器、转换规则)存储在外部(如数据库或配置文件),然后在每次创建状态机时从外部源加载配置。 步骤: 1. 定义状态和触发器的枚举(或者使用字符串等动态类型,但Stateless要求状态和触发器是同一类型,且通常使用枚举)。但是,如果我们希望完全动态,我们可以考虑使用字符串来表示状态和触发器。 2. 设计一个存储状态机配置的方式,例如,我们可以有一个配置列表,每个配置项包括:当前状态、触发器、目标状态(或者动态选择目标状态的方法)。 3. 在创建状态机实例时,从存储中读取配置,然后通过Stateless提供的API动态配置状态机。 但是,Stateless框架的状态机配置是在编译时类型安全的,它通常要求状态和触发器是枚举。然而,我们可以通过使用`StateMachine<string, string>`来使用字符串作为状态和触发器,从而实现动态配置。 示例代码(C#): 假设我们有一个状态机配置的列表,每个配置项包含:当前状态(CurrentState)、触发器(Trigger)、目标状态(NextState)以及是否使用动态选择(如果使用动态选择,则NextState可能是一个函数,但在配置列表中存储函数可能不太现实,所以我们可以考虑另一种方式:存储一个条件表达式或者委托的名称,然后通过反射调用。但这样会复杂,且可能不安全。因此,我们这里只考虑静态配置的转换,动态转换使用`PermitDynamic`,但需要预先注册委托)。 为了简化,我们可以这样设计: 1. 定义一个状态机配置类: public class StateTransitionConfig { public string CurrentState { get; set; } public string Trigger { get; set; } public string NextState { get; set; } // 如果使用静态转换 // 或者,我们可以有一个标志,指示是否动态选择状态,但动态选择需要提供委托,这需要我们在代码中预先定义好委托,然后通过配置触发器的名称来关联。 } 2. 从某个数据源(如数据库)读取配置列表。 3. 创建状态机并配置: var stateMachine = new StateMachine<string, string>("初始状态"); foreach (var config in configList) { // 获取当前状态的配置 var stateConfig = stateMachine.Configure(config.CurrentState); // 静态转换 stateConfig.Permit(config.Trigger, config.NextState); } 但是,如果我们想要动态选择目标状态(即根据运行时条件决定下一个状态),我们可以使用`PermitDynamic`,但需要提供一个委托。这个委托必须预先定义好,我们可以在配置中指定一个委托名称,然后通过一个字典映射到具体的委托方法。 例如: public delegate string DynamicStateSelector(); private Dictionary<string, DynamicStateSelector> _dynamicSelectors = new Dictionary<string, DynamicStateSelector>(); // 注册动态选择器 _dynamicSelectors.Add("Selector1", () => { // 根据一些条件返回状态字符串 return "State1"; }); // 在配置循环中,如果配置项标记为动态,则使用PermitDynamic if (config.IsDynamic) { var selector = _dynamicSelectors[config.SelectorName]; stateConfig.PermitDynamic(config.Trigger, selector); } 这样,我们就可以在运行时动态配置状态机了。 但是,请注意,无状态环境意味着我们不会保存状态机的状态,所以每次请求时,我们可能需要根据某个外部存储的状态值来初始化状态机,然后根据事件(触发器)进行状态转换,并保存新的状态。 总结:在无状态环境下动态创建状态机,我们可以通过以下步骤实现: 1.状态机的配置存储在外部(如数据库),包括状态转换规则(静态转换和动态转换的委托名称)。 2. 当需要处理一个事件时,从外部存储获取当前状态,然后根据配置动态构建状态机(使用字符串作为状态和触发器类型)。 3. 对于动态转换,预先定义一组委托(动态状态选择器),并在配置中通过名称引用。 4. 触发事件后,得到新状态,然后将新状态保存回外部存储。 这样,状态机的状态保存在外部存储中,而状态机的转换规则通过动态配置加载,从而实现了无状态环境下的状态机动态创建和使用。 注意:Stateless框架本身并不要求状态机实例是长期存在的,我们可以在每次需要时根据当前状态和配置重新创建状态机实例,然后触发事件,得到新状态,然后销毁状态机实例。这样,应用服务器本身就可以是无状态的。 示例代码: 假设我们有一个服务,它处理订单状态。订单状态存储在数据库中,而状态转换规则也存储在数据库中(或者配置中心)。 伪代码: public class OrderService { // 获取订单的当前状态(从数据库) string currentState = GetOrderState(orderId); // 从配置源加载该订单类型的状态机配置 var configs = LoadStateMachineConfigs(orderType); // 创建状态机 var sm = new StateMachine<string, string>(currentState); // 配置状态机 foreach (var config in configs) { var stateConfig = sm.Configure(config.CurrentState); if (config.IsDynamic) { // 获取预先注册的委托 var selector = GetDynamicSelector(config.SelectorName); stateConfig.PermitDynamic(config.Trigger, selector); } else { stateConfig.Permit(config.Trigger, config.NextState); } } // 触发事件(例如:用户支付) sm.Fire("Pay"); // 获取新状态 string newState = sm.State; // 保存新状态到数据库 SaveOrderState(orderId, newState); } 这样,每次处理事件时,我们都重新创建状态机,然后触发事件,保存新状态。由于状态机配置是从外部加载的,所以我们可以动态修改配置(比如添加新的状态转换规则)而无需重新部署应用。 但是,这种方法在频繁触发事件时可能会有性能问题,因为每次都要重新加载配置并构建状态机。我们可以通过缓存状态机配置来优化。 另外,引用[3]提到了一种动态生成状态机的方法,它使用声明式配置(通过Action类)来动态生成状态机。虽然这是另一个框架(MSM)的做法,但思路类似:将配置与代码分离,在运行时根据配置生成状态机。 因此,在Stateless框架中,我们可以通过上述方式实现动态创建状态机。 相关问题: 1. 动态状态机配置如何存储和管理? 2. 在无状态环境中,如何保证状态转换的并发安全? 3. 如何优化动态创建状态机的性能? 4. Stateless框架支持哪些动态配置方式? 5. 除了Stateless框架,还有哪些支持动态创建状态机的框架?</think>### 在无状态环境下动态创建状态机的方法 在无状态(Stateless)环境中动态创建状态机,核心思路是**将状态机配置与状态存储分离**,通过外部数据源驱动状态机的动态构建。以下是具体实现方法: --- #### 一、核心实现步骤 1. **状态配置外部化** 将状态转换规则存储在数据库或配置文件中,例如: | 当前状态 | 触发器 | 目标状态 | 动态条件 | |----------|--------|----------|----------| | `草稿` | `提交` | `审核中` | - | | `审核中` | `通过` | `已发布` | `IsAdmin`| | `审核中` | `驳回` | `草稿` | - | 2. **动态加载配置** 初始化时从外部源加载配置,动态构建状态机: ```csharp // 伪代码:从数据库加载配置 var transitions = db.GetTransitions(); var stateMachine = new StateMachine<string, string>("草稿"); foreach (var t in transitions) { stateMachine.Configure(t.CurrentState) .PermitIf(t.Trigger, t.NextState, () => CheckCondition(t.DynamicCondition)); } ``` 3. **使用动态触发器** 通过 `PermitDynamic()` 实现运行时状态决策(引用[4]): ```csharp // 根据业务数据动态选择目标状态 stateMachine.Configure("审核中") .PermitDynamic("分配", context => context.User.IsAdmin ? "高级处理" : "普通处理" ); ``` 4. **无状态设计** - **状态存储**:将当前状态保存在数据库/缓存中,而非内存 - **实例重建**:每次请求时根据存储的状态重建状态机: ```csharp var currentState = db.GetState(orderId); var sm = new StateMachine<string, string>(currentState); sm.Configure(...); // 动态加载配置 sm.Fire(trigger); // 触发事件 db.SaveState(orderId, sm.State); // 保存新状态 ``` --- #### 二、关键代码示例 ```csharp // 动态构建状态机(引用[2]扩展) public StateMachine<string, string> BuildStateMachine(string initialState) { var sm = new StateMachine<string, string>(initialState); var rules = LoadRulesFromDatabase(); // 从数据库加载配置 foreach (var rule in rules) { sm.Configure(rule.CurrentState) .OnEntry(() => LogEntry(rule.CurrentState)) .PermitDynamic(rule.Trigger, ctx => rule.UseDynamic ? CalculateNextState(ctx) : rule.NextState ); } return sm; } // 使用示例 var orderState = GetOrderStateFromDB(orderId); var sm = BuildStateMachine(orderState); sm.Fire("支付"); // 触发事件 SaveStateToDB(sm.State); // 保存新状态 ``` --- #### 三、解决无状态环境的关键点 1. **状态持久化** 每次操作后立即将新状态保存到外部存储(如数据库),确保下次请求可重建。 2. **配置热更新** 修改数据库中的转换规则即可动态调整状态机逻辑,无需重启服务。 3. **动态条件支持** 通过 `PermitIf` 和 `PermitDynamic` 实现运行时决策(引用[4]): ```csharp .PermitIf("提交", "审核中", () => DateTime.Now.Hour < 17) // 仅限工作时间提交 ``` 4. **上下文传递** 在触发事件时注入业务上下文: ```csharp sm.Fire("审核", new { User = currentUser }); // 上下文用于动态计算状态 ``` --- #### 四、适用场景 1. 需要运行时修改状态规则的业务(如工作流引擎) 2. 多租户系统中各租户拥有独立状态流 3. 状态转换依赖实时业务数据的场景(如风控系统) > **注意事项**:频繁重建状态机可能影响性能,建议通过缓存配置优化加载速度。对于超复杂状态机,可参考 MSM 的分层状态设计(引用[3])。 --- ### 相关问题 1. 如何保证动态状态机在高并发场景下的数据一致性? 2. Stateless 框架的 `PermitDynamic` 与 `PermitIf` 有何本质区别? 3. 在微服务架构中如何跨服务管理状态机? 4. 动态状态机配置变更时如何避免版本冲突? 5. 除了数据库,还有哪些存储方案适合保存状态机配置?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值