65.9K
CodeProject 正在变化。 阅读更多。
Home

一个简单的状态机

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (26投票s)

2009年10月29日

CPOL

3分钟阅读

viewsIcon

126341

downloadIcon

4652

使用有限状态自动机 (FSM) 模型创建松散耦合的状态。

引言

我不会解释自动化和状态设计模式理论 - 您可以在互联网上找到大量的信息和示例。这里有一些已知资源的链接

在本文中,我想专注于状态机 (SM) 的一种可能的实现,它可以解决状态之间紧密连接的问题。

背景

在状态机的经典实现中,每个对象在被状态变化触发时,会执行某些动作,然后要么改变到下一个状态,要么“回滚”到前一个状态。这种方法的缺点是状态对象之间的紧密连接,每个对象都必须知道它的“邻居”。状态机逻辑的改变可能会导致状态对象的多次改变。

一种解决方案是构建一个状态机,它将自动化逻辑和状态变化嵌入到转换中,并且状态对象不会相互连接。这种设计允许更改自动化逻辑而不更改对象,从而简化了复杂状态机的构建。

此外,在下面的示例中,您可以找到所谓的“自动转换” - 转换,其中某个状态是中间状态,并且在转换结束时必须自动转到另一个状态。在本例中,这样的状态由“下一个状态”表示,它会自动转到“播放”状态,并由自动转换“next2play_transition”描述。

使用代码

让我们首先回顾一下AbstractFSM.dll,它实现了一个状态机的基本模型

AbstractFSM_demo

  1. State 类 - 描述一个简单的状态(不包括任何逻辑,当然,以后可以添加 - 所有逻辑都由动作描述)
  2. public class State
    {
       private String m_sState = null;
       public State(string sSate)  {m_sState = sState;} 
       protected virtual void ChangeState( object sender, StateEventArgs eventArgs) {}
       public override string ToString() { return m_sState; }
    }
  3. Transition / Transitions - 描述从一个状态到另一个状态的转换逻辑,并定义转换期间执行的动作
  4. //actions that are performed when state changed
    public delegate void StateAction(object sender, StateEventArgs eventArgs);
    public class Transition
    {
       private State m_initialState;
       private State initialState
       {
           get {return m_initialState;}
       } 
       
       private State m_finalState;
       private State finalState
       {
           get {return m_finalState;}
       } 
       private StateAction m_state_action;
       public StateAction action
       {
           get {return m_state_action;}
       }
       private bool m_autoMode = false;
       public bool AutoMode
       {
          get {return m_autoMode;}
       } 
       private Transition m_autoTransition = null;
       public Transition AutoTransition
       {
          get {return m_autoTransition;}
       }
       //Constructors
       public Transition(State initialState, StateEventArgs sevent, 
                         State finalState, StateAction action)
       {
          m_initialState = initialState;
          m_eventArgs = sevent;
          m_finalState = finalState;
          m_state_action = action;
       }
       
       public Transition(State initialState, StateEventArgs sevent, 
                         State finalState, StateAction action,
                         bool AutoMode, Transition autoTransition)
                : this (initialState, sevent, finalState, action)  
       {
          m_autoMode = autoMode;
          m_autoTransition = autoTransition;
       }  
       //get a unique transition key
       public override int GetHashCode()
       {
          return GetHashCode(m_initialState, n_eventArgs);
       } 
       public static int GetHashCode(State state, StateEventArgs sevent)
       {
           return (state.GetHashCode() << 8) + sevent.Id;
       }
    
    
       /// <summary>Represents a collection of transition objects.</summary>
        public class Transitions : 
               System.Collections.Generic.Dictionary <int, Transition>   
        {
            /// <summary>Adds the specified transition to the collection.</summary> 
            /// <param name="transition">Transition object</param>      
            /// <see cref="System.Collections.Generic.Dictionary {int, Transition}"/>
            /// <exception cref="System.ArgumentNullException">Key is null</exception>
            /// <exception cref="System.ArgumentException">
            ///      An transition with the same key already exists.</exception>
            public void Add(Transition transition)
            {
                // The Add method throws an exception
                // if the new key is already in the dictionary.
                try
                {
                    base.Add(transition.GetHashCode(), transition);
                }
                catch (ArgumentException)
                {
                    throw new ArgumentException(
                            "A transition with the key (Initials state " + 
                            transition.initialState + ", Event " + 
                            transition.eventArgs + ") already exists.");
                }  
            }
            //
            public Transition this[State state, StateEventArgs sevent]
            {
                get
                {
                    try
                    {
                        return this[Transition.GetHashCode(state, sevent)];
                    }
                    catch(KeyNotFoundException)
                    {
                        throw new KeyNotFoundException(
                                  "The given transition was not found.");
                    }
                }
                set
                {
                    this[Transition.GetHashCode(state, sevent)] = value;
                }
            }
            //
            public bool Remove(State state, StateEventArgs sevent)
            {
                return base.Remove(Transition.GetHashCode(state, sevent));   
            }
        }
  5. IStateManager / StateManager - 管理状态和转换逻辑
  6. public interface IStateManager : IDisposable 
        {
            void ChangeState(object sender, StateEventArgs eventArgs);
            bool CheckState(object sender, StateEventArgs eventArgs);
        }
        public abstract class StatesManager : IStateManager
        {
            // Declare the delegate (if using non-generic pattern).
            public delegate void StateChangedEventHandler(object sender, 
                                 StateEventArgs eventArgs);
            // Declare the event.
            public event StateChangedEventHandler StateChanged;
    
            public StatesManager()
            {
                //build transitions and set an initial state
                m_activeState = BuildTransitionsTable(); 
            }
    
            public virtual void Dispose()
            {
                //virtual method
            }
            State m_activeState = null;
            public State ActiveState
            {
                get { return m_activeState; }
            }
    
            Transitions m_transitions = new Transitions();
            public Transitions Transitions
            {
                get { return m_transitions; }
            }
            //returns initial state
            protected abstract State BuildTransitionsTable();
            //
            public virtual void ChangeState(object sender, StateEventArgs eventArgs)
            {
                Transition transition = m_transitions[m_activeState, eventArgs];
                m_activeState = transition.finalState;
                //raise 'StateChanged' event
                 if (StateChanged != null)
                     StateChanged(this, eventArgs); 
                if (transition.action != null)
                    transition.action(this, eventArgs);
                //if the transitional is automatic - automatically go to the next state:
                if (transition.AutoMode == true && transition.AutoTransition != null)
                {
                    m_activeState = transition.AutoTransition.initialState;
                    ChangeState(sender, transition.AutoTransition.eventArgs);    
                }
            }
            public virtual bool CheckState(object sender, StateEventArgs eventArgs)
            {
                return m_transitions.ContainsKey(
                       Transition.GetHashCode(m_activeState, eventArgs));
            } 
        }

现在,让我们开始构建一个具体的状态管理器。我将其称为“MediaPlayerStateManager” - 创建和控制状态机的基本类。我想提请您注意 BuildTransitionsTable() 函数,它创建了自动化逻辑。让我们回顾一下使用虚拟媒体播放器定义自动化逻辑的示例,该媒体播放器具有停止、暂停、播放、上一个和下一个按钮。假设我们需要描述从暂停状态到播放状态的转换。为此,我们将首先构造一个委托 OnPlay,它将在状态改变时执行,并描述转换

Transitions.Add(new Transition(pause_state, 
    new StateEventArgs((int)StateEvents.Play), play_state, play_action));

以下是此命令所说的内容(用通俗易懂的英语):当在暂停状态下发生 Play 事件时,执行 OnPlay 动作并转到播放状态。还有所谓的“自动转换”,它描述了中间状态。在此示例中,这样的状态可以由下一个状态表示 – 当按下“下一个”按钮时,状态机将首先转到下一个状态,然后再转到播放状态。

Transitions.Add(new Transition(play_state, new StateEventArgs((int)StateEvents.Next), 
            next_state, next_action, true, next2play_transition));

下面显示的是 MediaPlayerStateManager 的完整代码

class MediaPlayerStateManager : StatesManager
{

    private frmTest m_playWindow = null;
    public MediaPlayerStateManager(frmTest playWindow)
        : base()
    {
        m_playWindow = playWindow;
        ChangeState(this, new StateEventArgs((int)StateEvents.Stop));
    }
    protected override State BuildTransitionsTable()
    {
        //create states
        State stop_state = new State("Stop");   //stop pressed
        State play_state = new State("Play");   //play pressed
        State pause_state = new State("Pause"); //pause pressed
        State previous_state = new State("Previous"); //prev. song selected
        State next_state = new State("Next");   //next song selected
        //actions
        StateAction stop_action = new StateAction(OnStop);
        StateAction play_action = new StateAction(OnPlay);
        StateAction pause_action = new StateAction(OnPause);
        StateAction previous_action = new StateAction(OnPrevious);
        StateAction next_action = new StateAction(OnNext);
        //
        //clear transitions
        Transitions.Clear();
        //////////////////   build transitions table  ///////////////////////
        //pause state
        Transitions.Add(new Transition(pause_state, 
                        new StateEventArgs((int)StateEvents.Play), 
                        play_state, play_action));
        Transitions.Add(new Transition(pause_state, 
                        new StateEventArgs((int)StateEvents.Stop), 
                        stop_state, stop_action));
        //previous state
        Transition prev2play_transition = new Transition(previous_state, 
                   new StateEventArgs((int)StateEvents.Play), 
                   play_state, play_action);
        Transitions.Add(prev2play_transition);
        Transition next2play_transition = new Transition(next_state, 
                   new StateEventArgs((int)StateEvents.Play), 
                   play_state, play_action);
        Transitions.Add(next2play_transition);
        //stop state
        Transitions.Add(new Transition(stop_state, 
                        new StateEventArgs((int)StateEvents.Play), 
                        play_state, play_action));
        //play state
        Transitions.Add(new Transition(play_state, 
                        new StateEventArgs((int)StateEvents.Stop), 
                        stop_state, stop_action));
        Transitions.Add(new Transition(play_state, 
                        new StateEventArgs((int)StateEvents.Pause), 
                        pause_state, pause_action));
        Transitions.Add(new Transition(play_state, 
                        new StateEventArgs((int)StateEvents.Previos), 
                        previous_state, previous_action, true, 
                        prev2play_transition));
        Transitions.Add(new Transition(play_state, 
                        new StateEventArgs((int)StateEvents.Next), 
                        next_state, next_action, true, next2play_transition));

        return play_state;
    }

    public override void ChangeState(object sender, StateEventArgs eventArgs)
    {
        try
        {
            Transition transition = Transitions[ActiveState, eventArgs];
            m_playWindow.lstStates.Items.Insert(
               0, m_playWindow.lstStates.Items.Count.ToString("000") +
               " - State '" + transition.initialState.ToString() +
               "' was changed to a new state '" + 
               transition.finalState.ToString() +
               "' by event " + 
               Enum.GetName(typeof(StateEvents), eventArgs.Id));
            base.ChangeState(sender, eventArgs);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK, 
                            MessageBoxIcon.Error);
        }
    }
    private void OnStop(object sender, StateEventArgs sevent)
    {
        //m_playWindow.btnStop.Enabled = false;  
        m_playWindow.txtStatus.Text = "Stopped";
    }
    private void OnPlay(object sender, StateEventArgs sevent)
    {
        m_playWindow.txtStatus.Text = "Playing song '" + 
                     m_playWindow.lstSongs.SelectedItem.ToString();
    }
    private void OnPause(object sender, StateEventArgs sevent)
    {

        m_playWindow.txtStatus.Text = "Paused";
    }
    private void OnPrevious(object sender, StateEventArgs sevent)
    {
        m_playWindow.lstSongs.SelectedIndex -= 1;
        if (m_playWindow.lstSongs.SelectedIndex < 0)
            m_playWindow.lstSongs.SelectedIndex = 0;
    }
    private void OnNext(object sender, StateEventArgs sevent)
    {
        if (m_playWindow.lstSongs.SelectedIndex + 1 >= m_playWindow.lstSongs.Items.Count)
            m_playWindow.lstSongs.SelectedIndex = m_playWindow.lstSongs.Items.Count - 1;
        else
            m_playWindow.lstSongs.SelectedIndex += 1;
    }
}

构建自动化逻辑后,剩下的就是添加具体的动作处理程序,这些处理程序将在状态转换期间自动执行。这种方法与经典的状态设计模式相反,在经典的状态设计模式中,动作由状态对象本身执行,但它更适合于此状态机模型(如果需要,可以将动作移动到状态对象)。

这是我的第一篇文章,欢迎批评和评论。

就这样! 我希望我不是唯一一个能从我的工作中受益的人。 特别感谢所有在此处发表了关于状态机的优秀文章的作者。

© . All rights reserved.