服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - 一个状态机的实现

一个状态机的实现

2021-12-22 13:55诺贝尔 C#

本文主要介绍了C#实现一个状态机的思路与方法,具有很好的参考价值,下面跟着小编一起来看下吧

话不多说,先看代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,那么选择器就是其中一个。至于怎么选,就让用户来定义实际的选择器了。

?
1
2
3
4
5
6
7
8
9
10
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 读取结果序列(只是不知道有什么用)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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函数。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
  }

做的事也很简单,就是调用前件处理程序,处理成功就转移状态,否则退出。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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个状态的网状结构,结果有点眼花缭乱。要是能统一起来估计会更好一些。

要关注的是第一个状态,和最后的状态的构造,否则无法停机,嵌入死循环。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//测试代码
//---------创建部分---------
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;

构造完毕后,就可以使用这个状态机了。

?
1
2
3
4
5
6
7
8
9
//选择器跳转  
mess += "选择器跳转:\n------------------------\n";
foreach (var stor in cont)
    mess+=$"状态转变次数:{stor}\n";
//直接控制跳转
mess += "\n直接控制状态跳转:\n------------------------\n";
cont.transition(s1);
cont.transition(s2);
cont.transition(s3);

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持服务器之家!

原文链接:http://www.cnblogs.com/Nobel/p/6361595.html

延伸 · 阅读

精彩推荐
  • C#如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

    如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

    这篇文章主要给大家介绍了关于如何使用C#将Tensorflow训练的.pb文件用在生产环境的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴...

    bbird201811792022-03-05
  • C#深入理解C#的数组

    深入理解C#的数组

    本篇文章主要介绍了C#的数组,数组是一种数据结构,详细的介绍了数组的声明和访问等,有兴趣的可以了解一下。...

    佳园9492021-12-10
  • C#C#设计模式之Strategy策略模式解决007大破密码危机问题示例

    C#设计模式之Strategy策略模式解决007大破密码危机问题示例

    这篇文章主要介绍了C#设计模式之Strategy策略模式解决007大破密码危机问题,简单描述了策略模式的定义并结合加密解密算法实例分析了C#策略模式的具体使用...

    GhostRider10972022-01-21
  • C#SQLite在C#中的安装与操作技巧

    SQLite在C#中的安装与操作技巧

    SQLite,是一款轻型的数据库,用于本地的数据储存。其优点有很多,下面通过本文给大家介绍SQLite在C#中的安装与操作技巧,感兴趣的的朋友参考下吧...

    蓝曈魅11162022-01-20
  • C#利用C#实现网络爬虫

    利用C#实现网络爬虫

    这篇文章主要介绍了利用C#实现网络爬虫,完整的介绍了C#实现网络爬虫详细过程,感兴趣的小伙伴们可以参考一下...

    C#教程网11852021-11-16
  • C#VS2012 程序打包部署图文详解

    VS2012 程序打包部署图文详解

    VS2012虽然没有集成打包工具,但它为我们提供了下载的端口,需要我们手动安装一个插件InstallShield。网上有很多第三方的打包工具,但为什么偏要使用微软...

    张信秀7712021-12-15
  • C#三十分钟快速掌握C# 6.0知识点

    三十分钟快速掌握C# 6.0知识点

    这篇文章主要介绍了C# 6.0的相关知识点,文中介绍的非常详细,通过这篇文字可以让大家在三十分钟内快速的掌握C# 6.0,需要的朋友可以参考借鉴,下面来...

    雨夜潇湘8272021-12-28
  • C#C#微信公众号与订阅号接口开发示例代码

    C#微信公众号与订阅号接口开发示例代码

    这篇文章主要介绍了C#微信公众号与订阅号接口开发示例代码,结合实例形式简单分析了C#针对微信接口的调用与处理技巧,需要的朋友可以参考下...

    smartsmile20127762021-11-25