一个简单的状态机
使用有限状态自动机 (FSM) 模型创建松散耦合的状态。
引言
我不会解释自动化和状态设计模式理论 - 您可以在互联网上找到大量的信息和示例。这里有一些已知资源的链接
在本文中,我想专注于状态机 (SM) 的一种可能的实现,它可以解决状态之间紧密连接的问题。
- https://codeproject.org.cn/KB/cs/statemachinetoolkitparti.aspx
- http://www.dofactory.com/Patterns/PatternState.aspx
- http://en.wikipedia.org/wiki/Finite-state_machine
背景
在状态机的经典实现中,每个对象在被状态变化触发时,会执行某些动作,然后要么改变到下一个状态,要么“回滚”到前一个状态。这种方法的缺点是状态对象之间的紧密连接,每个对象都必须知道它的“邻居”。状态机逻辑的改变可能会导致状态对象的多次改变。
一种解决方案是构建一个状态机,它将自动化逻辑和状态变化嵌入到转换中,并且状态对象不会相互连接。这种设计允许更改自动化逻辑而不更改对象,从而简化了复杂状态机的构建。
此外,在下面的示例中,您可以找到所谓的“自动转换” - 转换,其中某个状态是中间状态,并且在转换结束时必须自动转到另一个状态。在本例中,这样的状态由“下一个状态”表示,它会自动转到“播放”状态,并由自动转换“next2play_transition”描述。
使用代码
让我们首先回顾一下AbstractFSM.dll,它实现了一个状态机的基本模型
State
类 - 描述一个简单的状态(不包括任何逻辑,当然,以后可以添加 - 所有逻辑都由动作描述)Transition
/ Transitions - 描述从一个状态到另一个状态的转换逻辑,并定义转换期间执行的动作IStateManager
/ StateManager - 管理状态和转换逻辑
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; }
}
//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));
}
}
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;
}
}
构建自动化逻辑后,剩下的就是添加具体的动作处理程序,这些处理程序将在状态转换期间自动执行。这种方法与经典的状态设计模式相反,在经典的状态设计模式中,动作由状态对象本身执行,但它更适合于此状态机模型(如果需要,可以将动作移动到状态对象)。
这是我的第一篇文章,欢迎批评和评论。
就这样! 我希望我不是唯一一个能从我的工作中受益的人。 特别感谢所有在此处发表了关于状态机的优秀文章的作者。