zoukankan      html  css  js  c++  java
  • 一个状态机的实现

    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}
    "; 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 += "选择器跳转:
    ------------------------
    ";
     
    foreach (var stor in cont)
                    mess+=$"状态转变次数:{stor}
    ";
                
    //直接控制跳转
    mess += "
    直接控制状态跳转:
    ------------------------
    ";
    
    cont.transition(s1);
    cont.transition(s2);
    cont.transition(s3);

    分享到此,谢谢。

  • 相关阅读:
    离线获取docker镜像(docker save vs load)&docker export vs import& docker commit
    深入分析JavaWeb的中文编码问题
    python中Django的基本使用
    element+springboot实现简单的商品管理
    springboot+thymeleaf自定义标签
    springboot整合shiro
    linux的namespace、docker网络模式
    Docker Compose、Swarm 集群管理
    iOS Share Extension 自定义分享界面
    Mixin Messenger 源码解读 1 — — WCDB Swift
  • 原文地址:https://www.cnblogs.com/Nobel/p/6361595.html
Copyright © 2011-2022 走看看