一個狀態(tài)機的實現(xiàn)
話不多說,先看代碼:
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; } }
狀態(tài)比較簡單,一個Name標識,一個后件狀態(tài)列表,然后一個狀態(tài)選擇器。
比如狀態(tài)a,可以轉(zhuǎn)移到狀態(tài)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); }
和狀態(tài)類State關(guān)注后件狀態(tài)不同,上下文類Context關(guān)注前件狀態(tài)。當跳轉(zhuǎn)到一個新的狀態(tài),這個過程中就要根據(jù)當前狀態(tài)來實施不同的策略。比如想進入狀態(tài)c,根據(jù)當前狀態(tài)是a, b,d 有不同的處理程序。這種轉(zhuǎn)移處理程序,是一一對應(yīng)的,所以用了 Tuple<進入的狀態(tài),當前狀態(tài)> 來描述一個跳轉(zhuǎn)鏈。然后用Dictionary 捆綁相關(guān)的處理程序。
上下文會攜帶 T Value 數(shù)據(jù),要怎么處理這種數(shù)據(jù)?我是通過ref 參數(shù)來傳遞給處理程序。因為我不想IState 關(guān)心上下文的構(gòu)造,它只需要關(guān)注實際的數(shù)據(jù) T value;
上下文保存數(shù)據(jù)和當前狀態(tài),然后通過transiton 讓用戶控制狀態(tài)的轉(zhuǎn)移。這里面有一個重復(fù),因為IState有選擇器來控制狀態(tài)轉(zhuǎn)移了。為什么要這么處理?我是為了構(gòu)造一個跳轉(zhuǎn)序列。引入IEnumerator和IEnumerable接口,然狀態(tài)可以在選擇器的作用下自動跳轉(zhuǎn),然后用foreach 讀取結(jié)果序列(只是不知道有什么用)。
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("必須設(shè)置初始狀態(tài)"); 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; // 要檢測冗余調(diào)用 protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: 釋放托管狀態(tài)(托管對象)。 } // TODO: 釋放未托管的資源(未托管的對象)并在以下內(nèi)容中替代終結(jié)器。 // TODO: 將大型字段設(shè)置為 null。 disposedValue = true; } } // TODO: 僅當以上 Dispose(bool disposing) 擁有用于釋放未托管資源的代碼時才替代終結(jié)器。 // ~Context() { // // 請勿更改此代碼。將清理代碼放入以上 Dispose(bool disposing) 中。 // Dispose(false); // } // 添加此代碼以正確實現(xiàn)可處置模式。 void IDisposable.Dispose() { // 請勿更改此代碼。將清理代碼放入以上 Dispose(bool disposing) 中。 Dispose(true); // TODO: 如果在以上內(nèi)容中替代了終結(jié)器,則取消注釋以下行。 // GC.SuppressFinalize(this); } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return this; } IEnumerator IEnumerable.GetEnumerator() { return this; } #endregion }
重點關(guān)注transition函數(shù)和MoveNext函數(shù)。
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; }
做的事也很簡單,就是調(diào)用前件處理程序,處理成功就轉(zhuǎn)移狀態(tài),否則退出。
bool IEnumerator.MoveNext() { //后件處理 IContext<T> context = this as IContext<T>; IState current = context.CurrentState; if (current == null) throw new Exception("必須設(shè)置初始狀態(tài)"); if (context.CurrentState.Selector != null) { IState next= context.CurrentState.Selector(context.CurrentState); return context.transition(next); } return false; }
MoveNext通過選擇器來選擇下一個狀態(tài)。
總的來說,我這個狀態(tài)機的實現(xiàn)只是一個框架,沒有什么功能,但是我感覺是比較容易編寫狀態(tài)轉(zhuǎn)移目錄樹的。
用戶首先要創(chuàng)建一組狀態(tài),然后建立目錄樹結(jié)構(gòu)。我的實現(xiàn)比較粗糙,因為用戶要分別構(gòu)建目錄樹,前件處理器,還有后件選擇器這三個部分。編寫測試代碼的時候,我寫了9個狀態(tài)的網(wǎng)狀結(jié)構(gòu),結(jié)果有點眼花繚亂。要是能統(tǒng)一起來估計會更好一些。
要關(guān)注的是第一個狀態(tài),和最后的狀態(tài)的構(gòu)造,否則無法停機,嵌入死循環(huán)。
//測試代碼 //---------創(chuàng)建部分--------- 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; //---------狀態(tài)處理器--------- 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); //---------狀態(tài)選擇器--------- 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;
構(gòu)造完畢后,就可以使用這個狀態(tài)機了。
//選擇器跳轉(zhuǎn) mess += "選擇器跳轉(zhuǎn):\n------------------------\n"; foreach (var stor in cont) mess+=$"狀態(tài)轉(zhuǎn)變次數(shù):{stor}\n"; //直接控制跳轉(zhuǎn) mess += "\n直接控制狀態(tài)跳轉(zhuǎn):\n------------------------\n"; cont.transition(s1); cont.transition(s2); cont.transition(s3);
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!