一个状态机的实现
发布时间 - 2026-01-10 22:49:33 点击率:次话不多说,先看代码:
interface IState
{
string Name { get; set; }
//后件处理
IList<IState> Nexts { get; set; }
Func<IState /*this*/, IState /*next*/> Selector { get; set; }
}
class State : IState
{
public string Name { get; set; } = "State";
IList<IState> IState.Nexts { get; set; } = new List<IState>();
public Func<IState, IState> Selector { get; set; }
}
状态比较简单,一个Name标识,一个后件状态列表,然后一个状态选择器。
比如状态a,可以转移到状态b,c,d,那么选择器就是其中一个。至于怎么选,就让用户来定义实际的选择器了。
delegate bool HandleType<T>(IState current, IState previous,ref T value);
interface IContext<T> : IEnumerator<T>, IEnumerable<T>
{
//data
T Value { get; set; }
//前件处理
IDictionary<Tuple<IState/*this*/, IState/*previous*/>, HandleType<T>> Handles { get; set; }
IState CurrentState { get; set; }
bool transition(IState next);
}
和状态类State关注后件状态不同,上下文类Context关注前件状态。当跳转到一个新的状态,这个过程中就要根据当前状态来实施不同的策略。比如想进入状态c,根据当前状态是a, b,d 有不同的处理程序。这种转移处理程序,是一一对应的,所以用了 Tuple<进入的状态,当前状态> 来描述一个跳转链。然后用Dictionary 捆绑相关的处理程序。
上下文会携带 T Value 数据,要怎么处理这种数据?我是通过ref 参数来传递给处理程序。因为我不想IState 关心上下文的构造,它只需要关注实际的数据 T value;
上下文保存数据和当前状态,然后通过transiton 让用户控制状态的转移。这里面有一个重复,因为IState有选择器来控制状态转移了。为什么要这么处理?我是为了构造一个跳转序列。引入IEnumerator和IEnumerable接口,然状态可以在选择器的作用下自动跳转,然后用foreach 读取结果序列(只是不知道有什么用)。
class Context<T> : IContext<T>
{
T data;
T IContext<T>.Value { get=>data ; set=>data = value; }
IDictionary<Tuple<IState, IState>, HandleType<T>> IContext<T>.Handles { get; set; }
= new Dictionary<Tuple<IState, IState>, HandleType<T>>();
public IState CurrentState { get; set;}
T IEnumerator<T>.Current => (this as IContext<T>).Value ;
object IEnumerator.Current => (this as IContext<T>).Value;
bool IContext<T>.transition(IState next)
{
IContext<T> context= this as IContext<T>;
if (context.CurrentState == null || context.CurrentState.Nexts.Contains(next))
{
//前件处理
var key = Tuple.Create(next, context.CurrentState);
if (context.Handles.ContainsKey(key) && context.Handles[key] !=null)
if (!context.Handles[key](next, context.CurrentState,ref this.data))
return false;
context.CurrentState = next;
return true;
}
return false;
}
bool IEnumerator.MoveNext()
{
//后件处理
IContext<T> context = this as IContext<T>;
IState current = context.CurrentState;
if (current == null)
throw new Exception("必须设置初始状态");
if (context.CurrentState.Selector != null)
{
IState next= context.CurrentState.Selector(context.CurrentState);
return context.transition(next);
}
return false;
}
void IEnumerator.Reset()
{
throw new NotImplementedException();
}
#region IDisposable Support
private bool disposedValue = false; // 要检测冗余调用
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: 释放托管状态(托管对象)。
}
// TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
// TODO: 将大型字段设置为 null。
disposedValue = true;
}
}
// TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
// ~Context() {
// // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
// Dispose(false);
// }
// 添加此代码以正确实现可处置模式。
void IDisposable.Dispose()
{
// 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
Dispose(true);
// TODO: 如果在以上内容中替代了终结器,则取消注释以下行。
// GC.SuppressFinalize(this);
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return this;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this;
}
#endregion
}
重点关注transition函数和MoveNext函数。
bool IContext<T>.transition(IState next)
{
IContext<T> context= this as IContext<T>;
if (context.CurrentState == null || context.CurrentState.Nexts.Contains(next))
{
//前件处理
var key = Tuple.Create(next, context.CurrentState);
if (context.Handles.ContainsKey(key) && context.Handles[key] !=null)
if (!context.Handles[key](next, context.CurrentState,ref this.data))
return false;
context.CurrentState = next;
return true;
}
return false;
}
做的事也很简单,就是调用前件处理程序,处理成功就转移状态,否则退出。
bool IEnumerator.MoveNext()
{
//后件处理
IContext<T> context = this as IContext<T>;
IState current = context.CurrentState;
if (current == null)
throw new Exception("必须设置初始状态");
if (context.CurrentState.Selector != null)
{
IState next= context.CurrentState.Selector(context.CurrentState);
return context.transition(next);
}
return false;
}
MoveNext通过选择器来选择下一个状态。
总的来说,我这个状态机的实现只是一个框架,没有什么功能,但是我感觉是比较容易编写状态转移目录树的。
用户首先要创建一组状态,然后建立目录树结构。我的实现比较粗糙,因为用户要分别构建目录树,前件处理器,还有后件选择器这三个部分。编写测试代码的时候,我写了9个状态的网状结构,结果有点眼花缭乱。要是能统一起来估计会更好一些。
要关注的是第一个状态,和最后的状态的构造,否则无法停机,嵌入死循环。
//测试代码
//---------创建部分---------
string mess = "";//3
IState s3 = new State() { Name = "s3" };
//2
IState s2 = new State() { Name = "s2" };
//1
IState s1 = new State() { Name = "s1" };
//---------组合起来---------
s1.Nexts = new List<IState> { s2, s3 };
s2.Nexts = new List<IState> { s1, s3 };
s3.Nexts = new List<IState> { }; //注意end写法
//---------上下文---------
//transition
IContext<int> cont = new Context<int> { CurrentState=s1};//begin
cont.Value = 0;
//---------状态处理器---------
HandleType<int> funcLaji = (IState current, IState previous, ref int v) => { mess += $"{current.Name}:垃圾{previous.Name}\n"; v++; return true; };
//1
cont.Handles.Add(Tuple.Create(s1 , default(IState)), funcLaji);
cont.Handles.Add(Tuple.Create(s1, s2), funcLaji);
//2
cont.Handles.Add(Tuple.Create(s2, s1), funcLaji);
//3
cont.Handles.Add(Tuple.Create(s3, s1), funcLaji);
cont.Handles.Add(Tuple.Create(s3, s2), funcLaji);
//---------状态选择器---------
var rval = new Random();
Func<int,int> round = x => rval.Next(x);
s1.Selector = st => round(2)==0? s2:s3;
s2.Selector = st => round(2)==0? s1:s3;
构造完毕后,就可以使用这个状态机了。
//选择器跳转
mess += "选择器跳转:\n------------------------\n";
foreach (var stor in cont)
mess+=$"状态转变次数:{stor}\n";
//直接控制跳转
mess += "\n直接控制状态跳转:\n------------------------\n";
cont.transition(s1);
cont.transition(s2);
cont.transition(s3);
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!
# 状态机
# 详解bash中的退出状态机制
# StateMachine 状态机机制深入解析
# C++有限状态机实现计算器小程序
# 简单理解Python中基于生成器的状态机
# javascript与有限状态机详解
# 浅析C# 状态机Stateless
# 跳转
# 选择器
# 我是
# 的是
# 网状结构
# 第一个
# 并在
# 没有什么
# 用了
# 很简单
# 写了
# 只是一个
# 其中一个
# 眼花缭乱
# 多说
# 设置为
# 这里面
# 这三个
# 时才
# 比较容易
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel如何实现邮件验证激活账户_Laravel内置MustVerifyEmail接口配置【步骤】
如何在搬瓦工VPS快速搭建网站?
html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】
Laravel如何使用.env文件管理环境变量?(最佳实践)
详解vue.js组件化开发实践
中国移动官方网站首页入口 中国移动官网网页登录
SQL查询语句优化的实用方法总结
laravel怎么配置Redis作为缓存驱动_laravel Redis缓存配置教程
Laravel如何操作JSON类型的数据库字段?(Eloquent示例)
Laravel路由怎么定义_Laravel核心路由系统完全入门指南
重庆市网站制作公司,重庆招聘网站哪个好?
东莞市网站制作公司有哪些,东莞找工作用什么网站好?
深圳网站制作的公司有哪些,dido官方网站?
使用C语言编写圣诞表白程序
php中::能调用final静态方法吗_final修饰静态方法调用规则【解答】
如何解决hover在ie6中的兼容性问题
EditPlus中的正则表达式 实战(2)
如何快速搭建二级域名独立网站?
Linux系统命令中screen命令详解
canvas 画布在主流浏览器中的尺寸限制详细介绍
Laravel如何配置和使用队列处理异步任务_Laravel队列驱动与任务分发实例
高端建站三要素:定制模板、企业官网与响应式设计优化
php做exe能调用系统命令吗_执行cmd指令实现方式【详解】
使用Dockerfile构建java web环境
Laravel如何设置自定义的日志文件名_Laravel根据日期或用户ID生成动态日志【技巧】
Win11怎么设置默认图片查看器_Windows11照片应用关联设置
Laravel如何创建自定义Artisan命令?(代码示例)
Laravel怎么连接多个数据库_Laravel多数据库连接配置
Laravel如何使用Vite进行前端资源打包?(配置示例)
Laravel的.env文件有什么用_Laravel环境变量配置与管理详解
如何在自有机房高效搭建专业网站?
在线制作视频的网站有哪些,电脑如何制作视频短片?
Laravel怎么使用artisan命令缓存配置和视图
用yum安装MySQLdb模块的步骤方法
Laravel如何自定义分页视图?(Pagination示例)
Laravel如何发送系统通知?(Notification渠道示例)
Laravel中的withCount方法怎么高效统计关联模型数量
HTML5段落标签p和br怎么选_文本排版常用标签对比【解答】
悟空浏览器如何设置小说背景色_悟空浏览器背景色设置【方法】
免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?
Laravel如何理解并使用服务容器(Service Container)_Laravel依赖注入与容器绑定说明
Laravel中DTO是什么概念_在Laravel项目中使用数据传输对象(DTO)
jQuery中的100个技巧汇总
Laravel如何使用缓存系统提升性能_Laravel缓存驱动和应用优化方案
黑客如何通过漏洞一步步攻陷网站服务器?
Laravel怎么清理缓存_Laravel optimize clear命令详解
Python文件异常处理策略_健壮性说明【指导】
bing浏览器学术搜索入口_bing学术文献检索地址
高性能网站服务器配置指南:安全稳定与高效建站核心方案
大同网页,大同瑞慈医院官网?

