作者 GitiJun 2016.11.30 20:17:36 写了58篇文章,回复33人, A Simple State Machine 阅读:1453· 评论:0· 喜欢:1 A Simple State Machine morgi_a, 23 Jan 2014 CPOL 4.61 (24 votes) Rate this: vote 1vote 2vote 3vote 4vote 5 Create loose coupled States using a Finite State Automation (FSM) model. • Download source code - 16.2 KB • Download demo - 9.44 KB Introduction I'm not going to explain automation and the State Design Pattern theory - you can find tons of information and examples on the Internet. Here are a few links to the known resources: In this article, I'd like to concentrate on one of many possible implementations of a State Machine (SM), which solves the problem of tight connection between states. • http://www.codeproject.com/KB/cs/statemachinetoolkitparti.aspx • http://www.dofactory.com/Patterns/PatternState.aspx • http://en.wikipedia.org/wiki/Finite-state_machine Background In the classic implementation of a State Machine, each object, when triggered by a change of state, performs certain actions, and then either changes to the next state or "rolls back" to a previous one. The drawback of such an approach is a tight connection between the state objects, each of those must be aware of its "neighbors". A change in the State Machine logic might cause multiple changes in the state object. One of the solutions is to build a State Machine which embeds automation logic and state changes into transitions, and the state objects won't be interconnected. Such a design allows changing automation logic without change in objects, thus simplifying the construction of complex State Machines. Also, in an example below, you can find the so-called "automatic transitions" - transitions, where a certain state is intermediate and by the end of the transition must automatically go to another state. In this example, such a state is represented by "Next state", which automatically goes to "Play" and is described by the automatic transition "next2play_transition". Using the code Let's start by reviewing AbstractFSM.dll, which implements a basic model of a State Machine: 1. State class - describes a simple state (doesn't include any logic, which, of course, can be added later - all logic is described by actions): Hide Copy Code 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; } } 2. Transition / Transitions - describes transition logic from one state to another and defines the action performed during the transition: Hide Shrink Copy Code //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; } /// Represents a collection of transition objects. public class Transitions : System.Collections.Generic.Dictionary { /// Adds the specified transition to the collection. /// Transition object /// /// Key is null /// /// An transition with the same key already exists. 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)); } } 3. IStateManager / StateManager - manages states and the transitions logic: Hide Shrink Copy Code 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)); } } Now, let's start constructing a concrete State Manager. I will call it 'MediaPlayerStateManager' - the basic class, which creates and controls State Machine. I'd like to draw your attention to theBuildTransitionsTable() function which creates the automation logic. Let's review an example of automation logic definition using a dummy Media Player, which has Stop, Pause, Play, Previous, and Next buttons. Let's say, we need to describe a transition from the Pause state to the Play state. To do this, we will first construct a delegate OnPlay, which will be executed in the case of a state change, and describe the transition: Hide Copy Code Transitions.Add(new Transition(pause_state, new StateEventArgs((int)StateEvents.Play), play_state, play_action)); Here is what this command says (in plane English): when the Play event occurs while in the Pause state, execute the OnPlay action and go to the Play state. There are also the so-called "automatic transitions", which describe the intermediate states. In this example, such a state can be represented by the Next state – when the "Next" button is pressed, the State Machine will first go to the Next state and then to the Play state. Hide Copy Code Transitions.Add(new Transition(play_state, new StateEventArgs((int)StateEvents.Next), next_state, next_action, true, next2play_transition)); Shown below is the full code of MediaPlayerStateManager: Hide Shrink Copy Code 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; } } After constructing the automation logic, all that's left is to add concrete action handlers, which will be executed automatically during state transition. Such an approach goes against the classic State design pattern, where an action is performed by a state object itself, but it's more appropriate to this State Machine model (if required, actions can be moved to the state objects). This is my very first article, so criticisms and comments are welcome. That's all! I hope that I won't be the only one who can benefit from my work. Special thanks go to all authors who have published excellent articles on State Machines here. License This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL) Share • EMAIL • • • • • About the Author morgi_a Software Developer (Senior) Dell Canada Canada 'Honored' BA in Computer Science and Database Systems. Has been engaging in software development for more than 15 years. Has deliberately chosen this path and has no compunctions about it at all. Has nasty habit of sharing his 'out-of-office' code hoping to get some positive feedback. 赞 | 1 赏 标签:none