Simple state machine example in C#?
更新:
再次感谢这些例子,它们非常有用,下面我不是说从他们那里拿走任何东西。
就我所理解的,目前给出的例子不是状态机吗?状态机通常只理解我们理解的一半?从这个意义上说,例子确实改变了状态,但这只能通过改变一个变量的值来表示(并且允许不同的值在不同的状态下改变),而通常状态机也应该改变它的行为,并且行为不仅在允许一个变量根据状态改变不同的值的意义上,而且我n允许对不同状态执行不同方法的意义。
还是我对状态机及其常见用途有误解?
最好的问候
原始问题:
我在C中发现了关于状态机和迭代器块的讨论,以及创建状态机的工具以及不适合C的东西,所以我发现了很多抽象的东西,但是作为一个noob,所有这些都有点令人困惑。
因此,如果有人能提供一个C源代码示例,实现一个具有3,4个状态的简单状态机,仅仅是为了获得它的要点,那就太好了。
让我们从这个简单的状态图开始:
我们有:
- 4种状态(不活动、活动、暂停和退出)
- 5种状态转换类型(开始命令、结束命令、暂停命令、恢复命令、退出命令)。
您可以通过多种方式将其转换为C,例如在当前状态和命令上执行switch语句,或者在转换表中查找转换。对于这个简单的状态机,我更喜欢使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | using System; using System.Collections.Generic; namespace Juliet { public enum ProcessState { Inactive, Active, Paused, Terminated } public enum Command { Begin, End, Pause, Resume, Exit } public class Process { class StateTransition { readonly ProcessState CurrentState; readonly Command Command; public StateTransition(ProcessState currentState, Command command) { CurrentState = currentState; Command = command; } public override int GetHashCode() { return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode(); } public override bool Equals(object obj) { StateTransition other = obj as StateTransition; return other != null && this.CurrentState == other.CurrentState && this.Command == other.Command; } } Dictionary<StateTransition, ProcessState> transitions; public ProcessState CurrentState { get; private set; } public Process() { CurrentState = ProcessState.Inactive; transitions = new Dictionary<StateTransition, ProcessState> { { new StateTransition(ProcessState.Inactive, Command.Exit), ProcessState.Terminated }, { new StateTransition(ProcessState.Inactive, Command.Begin), ProcessState.Active }, { new StateTransition(ProcessState.Active, Command.End), ProcessState.Inactive }, { new StateTransition(ProcessState.Active, Command.Pause), ProcessState.Paused }, { new StateTransition(ProcessState.Paused, Command.End), ProcessState.Inactive }, { new StateTransition(ProcessState.Paused, Command.Resume), ProcessState.Active } }; } public ProcessState GetNext(Command command) { StateTransition transition = new StateTransition(CurrentState, command); ProcessState nextState; if (!transitions.TryGetValue(transition, out nextState)) throw new Exception("Invalid transition:" + CurrentState +" ->" + command); return nextState; } public ProcessState MoveNext(Command command) { CurrentState = GetNext(command); return CurrentState; } } public class Program { static void Main(string[] args) { Process p = new Process(); Console.WriteLine("Current State =" + p.CurrentState); Console.WriteLine("Command.Begin: Current State =" + p.MoveNext(Command.Begin)); Console.WriteLine("Command.Pause: Current State =" + p.MoveNext(Command.Pause)); Console.WriteLine("Command.End: Current State =" + p.MoveNext(Command.End)); Console.WriteLine("Command.Exit: Current State =" + p.MoveNext(Command.Exit)); Console.ReadLine(); } } } |
出于个人喜好,我喜欢用
您可能希望使用现有的开放源码有限状态机之一。例如,可以在http://code.google.com/p/bbv common/wiki/statemachine上找到bbv.common.statemachine。它有一个非常直观、流畅的语法和许多功能,例如,进入/退出操作、转换操作、保护、分级、被动实现(在调用者的线程上执行)和主动实现(运行FSM的自己的线程,事件被添加到队列中)。
以朱丽叶为例,状态机的定义变得非常简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var fsm = new PassiveStateMachine<ProcessState, Command>(); fsm.In(ProcessState.Inactive) .On(Command.Exit).Goto(ProcessState.Terminated).Execute(SomeTransitionAction) .On(Command.Begin).Goto(ProcessState.Active); fsm.In(ProcessState.Active) .ExecuteOnEntry(SomeEntryAction) .ExecuteOnExit(SomeExitAction) .On(Command.End).Goto(ProcessState.Inactive) .On(Command.Pause).Goto(ProcessState.Paused); fsm.In(ProcessState.Paused) .On(Command.End).Goto(ProcessState.Inactive).OnlyIf(SomeGuard) .On(Command.Resume).Goto(ProcessState.Active); fsm.Initialize(ProcessState.Inactive); fsm.Start(); fsm.Fire(Command.Begin); |
更新:项目位置已移动到:https://github.com/appccelerate/statemachine
这是一个非常经典的有限状态机的例子,它模拟了一个非常简单的电子设备(比如电视机)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace fsm { class Program { static void Main(string[] args) { var fsm = new FiniteStateMachine(); Console.WriteLine(fsm.State); fsm.ProcessEvent(FiniteStateMachine.Events.PlugIn); Console.WriteLine(fsm.State); fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn); Console.WriteLine(fsm.State); fsm.ProcessEvent(FiniteStateMachine.Events.TurnOff); Console.WriteLine(fsm.State); fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn); Console.WriteLine(fsm.State); fsm.ProcessEvent(FiniteStateMachine.Events.RemovePower); Console.WriteLine(fsm.State); Console.ReadKey(); } class FiniteStateMachine { public enum States { Start, Standby, On }; public States State { get; set; } public enum Events { PlugIn, TurnOn, TurnOff, RemovePower }; private Action[,] fsm; public FiniteStateMachine() { this.fsm = new Action[3, 4] { //PlugIn, TurnOn, TurnOff, RemovePower {this.PowerOn, null, null, null}, //start {null, this.StandbyWhenOff, null, this.PowerOff}, //standby {null, null, this.StandbyWhenOn, this.PowerOff} }; //on } public void ProcessEvent(Events theEvent) { this.fsm[(int)this.State, (int)theEvent].Invoke(); } private void PowerOn() { this.State = States.Standby; } private void PowerOff() { this.State = States.Start; } private void StandbyWhenOn() { this.State = States.Standby; } private void StandbyWhenOff() { this.State = States.On; } } } } |
这里有些无耻的自我宣传,但不久前我创建了一个名为yieldmachine的库,它允许以一种非常干净和简单的方式描述一个有限的复杂状态机。例如,考虑灯:
注意,这个状态机有2个触发器和3个状态。在yieldmachine代码中,我们为所有与状态相关的行为编写了一个单一的方法,在这种方法中,我们犯下了对每个状态使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class Lamp : StateMachine { // Triggers (or events, or actions, whatever) that our // state machine understands. [Trigger] public readonly Action PressSwitch; [Trigger] public readonly Action GotError; // Actual state machine logic protected override IEnumerable WalkStates() { off: Console.WriteLine("off."); yield return null; if (Trigger == PressSwitch) goto on; InvalidTrigger(); on: Console.WriteLine("*shiiine!*"); yield return null; if (Trigger == GotError) goto error; if (Trigger == PressSwitch) goto off; InvalidTrigger(); error: Console.WriteLine("-err-"); yield return null; if (Trigger == PressSwitch) goto off; InvalidTrigger(); } } |
又矮又好,嗯!
此状态机仅通过向其发送触发器进行控制:
1 2 3 4 5 6 7 | var sm = new Lamp(); sm.PressSwitch(); //go on sm.PressSwitch(); //go off sm.PressSwitch(); //go on sm.GotError(); //get error sm.PressSwitch(); //go off |
为了澄清,我在第一个状态中添加了一些注释,以帮助您了解如何使用它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | protected override IEnumerable WalkStates() { off: // Each goto label is a state Console.WriteLine("off."); // State entry actions yield return null; // This means"Wait until a // trigger is called" // Ah, we got triggered! // perform state exit actions // (none, in this case) if (Trigger == PressSwitch) goto on; // Transitions go here: // depending on the trigger // that was called, go to // the right state InvalidTrigger(); // Throw exception on // invalid trigger ... |
这是因为C编译器实际上在内部为每个使用
但是你不需要真正理解内部结构就可以使用它。
您可以对迭代器块进行编码,使您能够以协调的方式执行代码块。代码块是如何被分解的,实际上并不需要与任何内容对应,这只是您想要如何对其进行编码。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | IEnumerable<int> CountToTen() { System.Console.WriteLine("1"); yield return 0; System.Console.WriteLine("2"); System.Console.WriteLine("3"); System.Console.WriteLine("4"); yield return 0; System.Console.WriteLine("5"); System.Console.WriteLine("6"); System.Console.WriteLine("7"); yield return 0; System.Console.WriteLine("8"); yield return 0; System.Console.WriteLine("9"); System.Console.WriteLine("10"); } |
在这种情况下,当您调用counttoten时,实际上还没有执行任何操作。您得到的是一个有效的状态机生成器,您可以为此创建一个新的状态机实例。您可以通过调用getEnumerator()来实现这一点。结果IEnumerator实际上是一个状态机,可以通过调用moveNext(…)来驱动它。
因此,在本例中,第一次调用moveNext(…)时,将看到写入控制台的"1",下一次调用moveNext(…)时,将看到2、3、4,然后是5、6、7,然后是8,然后是9、10。正如您所看到的,它是一个很有用的机制,用于协调事情应该如何发生。
我在这里发布了另一个答案,因为这是从不同角度的状态机;非常直观。
我最初的答案是克拉西克不可侵犯的密码。我认为它在代码运行时非常直观,因为数组使得状态机的可视化变得简单。缺点是你必须把这些都写下来。REMOS的回答减轻了编写锅炉板代码的工作量,但视觉效果要差得多。还有第三种选择;真正地绘制状态机。
如果您正在使用.NET并且可以以运行时版本4为目标,那么您可以选择使用工作流的状态机活动。这些本质上允许您绘制状态机(就像在Juliet的图表中一样),并让WF运行时为您执行它。
请参阅MSDN文章用Windows工作流基础构建状态机以获得更多细节,并将此COMPUTEX站点作为最新版本。
这是我在以.NET为目标时最喜欢的选项,因为它很容易被非程序员看到、更改和解释;图片就像他们说的那样,值一千个字!
记住状态机是一个抽象的概念是很有用的,并且您不需要特定的工具来创建它,但是工具是有用的。
例如,您可以实现具有以下功能的状态机:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | void Hunt(IList<Gull> gulls) { if (gulls.Empty()) return; var target = gulls.First(); TargetAcquired(target, gulls); } void TargetAcquired(Gull target, IList<Gull> gulls) { var balloon = new WaterBalloon(weightKg: 20); this.Cannon.Fire(balloon); if (balloon.Hit) { TargetHit(target, gulls); } else TargetMissed(target, gulls); } void TargetHit(Gull target, IList<Gull> gulls) { Console.WriteLine("Suck on it {0}!", target.Name); Hunt(gulls); } void TargetMissed(Gull target, IList<Gull> gulls) { Console.WriteLine("I'll get ya!"); TargetAcquired(target, gulls); } |
这台机器会搜寻海鸥,并试图用水球打它们。如果它错过了,它将尝试发射一个直到击中(可能与一些现实的期望;)),否则它将在控制台中幸灾乐祸。它继续捕猎,直到它摆脱海鸥的骚扰。
每个函数对应于每个状态;不显示开始和结束(或接受)状态。不过,其中的状态可能比由函数建模的多。例如,在发射了气球之后,这台机器确实处于与之前不同的状态,但我认为这种区别是不切实际的。
一种常见的方法是使用类来表示状态,然后以不同的方式将它们连接起来。
今天我深入研究国家设计模式。我做了并测试了threadstate,它等于(+/-)在c中的线程,如图所示,在c中的线程。#
您可以轻松地添加新状态,配置从一个状态移动到另一个状态非常容易,因为它包含在状态实现中。
实现和使用at:按状态设计模式实现.NET线程状态
在网上找到了这个很好的教程,它帮助我在有限状态机上绕圈子。
http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867
本教程是语言不可知论,因此它可以很容易地适应您的C需求。
此外,使用的例子(蚂蚁寻找食物)很容易理解。
从教程中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | public class FSM { private var activeState :Function; // points to the currently active state function public function FSM() { } public function setState(state :Function) :void { activeState = state; } public function update() :void { if (activeState != null) { activeState(); } } } public class Ant { public var position :Vector3D; public var velocity :Vector3D; public var brain :FSM; public function Ant(posX :Number, posY :Number) { position = new Vector3D(posX, posY); velocity = new Vector3D( -1, -1); brain = new FSM(); // Tell the brain to start looking for the leaf. brain.setState(findLeaf); } /** * The"findLeaf" state. * It makes the ant move towards the leaf. */ public function findLeaf() :void { // Move the ant towards the leaf. velocity = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y); if (distance(Game.instance.leaf, this) <= 10) { // The ant is extremelly close to the leaf, it's time // to go home. brain.setState(goHome); } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Mouse cursor is threatening us. Let's run away! // It will make the brain start calling runAway() from // now on. brain.setState(runAway); } } /** * The"goHome" state. * It makes the ant move towards its home. */ public function goHome() :void { // Move the ant towards home velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y); if (distance(Game.instance.home, this) <= 10) { // The ant is home, let's find the leaf again. brain.setState(findLeaf); } } /** * The"runAway" state. * It makes the ant run away from the mouse cursor. */ public function runAway() :void { // Move the ant away from the mouse cursor velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y); // Is the mouse cursor still close? if (distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) { // No, the mouse cursor has gone away. Let's go back looking for the leaf. brain.setState(findLeaf); } } public function update():void { // Update the FSM controlling the"brain". It will invoke the currently // active state function: findLeaf(), goHome() or runAway(). brain.update(); // Apply the velocity vector to the position, making the ant move. moveBasedOnVelocity(); } (...) } |
我还没有尝试在C语言中实现FSM,但是这些声音(或外观)与我过去用C或ASM等低级语言处理FSM的方式非常复杂。
我相信我一直知道的方法叫做"迭代循环"。在它中,您基本上有一个"while"循环,它根据事件(中断)定期退出,然后再次返回到主循环。
在中断处理程序中,您将传递一个currentState并返回一个nextState,然后在主循环中覆盖currentState变量。在程序关闭(或微控制器复位)之前,您可以无限量地执行此操作。
我看到的其他答案看起来都非常复杂,相比之下,在我看来,一个FSM是如何实现的;它的美在于它的简单性和FSM可以非常复杂,有许多状态和转换,但它们允许复杂的过程很容易被分解和消化。
我意识到我的回答不应该包括另一个问题,但我不得不问:为什么这些其他建议的解决方案看起来如此复杂?它们似乎类似于用一个巨大的大锤敲击一颗小钉子。
我刚刚贡献了:
https://code.google.com/p/ysharp/source/browse/svn%2ftrunk%2fstateMachinesPoc
下面是演示直接和间接发送命令的示例之一,其中状态为iobserver(信号),从而响应信号源iobservable(信号):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Test { using Machines; public static class WatchingTvSampleAdvanced { // Enum type for the transition triggers (instead of System.String) : public enum TvOperation { Plug, SwitchOn, SwitchOff, Unplug, Dispose } // The state machine class type is also used as the type for its possible states constants : public class Television : NamedState<Television, TvOperation, DateTime> { // Declare all the possible states constants : public static readonly Television Unplugged = new Television("(Unplugged TV)"); public static readonly Television Off = new Television("(TV Off)"); public static readonly Television On = new Television("(TV On)"); public static readonly Television Disposed = new Television("(Disposed TV)"); // For convenience, enter the default start state when the parameterless constructor executes : public Television() : this(Television.Unplugged) { } // To create a state machine instance, with a given start state : private Television(Television value) : this(null, value) { } // To create a possible state constant : private Television(string moniker) : this(moniker, null) { } private Television(string moniker, Television value) { if (moniker == null) { // Build the state graph programmatically // (instead of declaratively via custom attributes) : Handler<Television, TvOperation, DateTime> stateChangeHandler = StateChange; Build ( new[] { new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = stateChangeHandler }, new { From = Television.Unplugged, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler }, new { From = Television.Off, When = TvOperation.SwitchOn, Goto = Television.On, With = stateChangeHandler }, new { From = Television.Off, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler }, new { From = Television.Off, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler }, new { From = Television.On, When = TvOperation.SwitchOff, Goto = Television.Off, With = stateChangeHandler }, new { From = Television.On, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler }, new { From = Television.On, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler } }, false ); } else // Name the state constant : Moniker = moniker; Start(value ?? this); } // Because the states' value domain is a reference type, disallow the null value for any start state value : protected override void OnStart(Television value) { if (value == null) throw new ArgumentNullException("value","cannot be null"); } // When reaching a final state, unsubscribe from all the signal source(s), if any : protected override void OnComplete(bool stateComplete) { // Holds during all transitions into a final state // (i.e., stateComplete implies IsFinal) : System.Diagnostics.Debug.Assert(!stateComplete || IsFinal); if (stateComplete) UnsubscribeFromAll(); } // Executed before and after every state transition : private void StateChange(IState<Television> state, ExecutionStep step, Television value, TvOperation info, DateTime args) { // Holds during all possible transitions defined in the state graph // (i.e., (step equals ExecutionStep.LeaveState) implies (not state.IsFinal)) System.Diagnostics.Debug.Assert((step != ExecutionStep.LeaveState) || !state.IsFinal); // Holds in instance (i.e., non-static) transition handlers like this one : System.Diagnostics.Debug.Assert(this == state); switch (step) { case ExecutionStep.LeaveState: var timeStamp = ((args != default(DateTime)) ? String.Format("\t\t(@ {0})", args) : String.Empty); Console.WriteLine(); // 'value' is the state value that we are transitioning TO : Console.WriteLine("\tLeave :\t{0} -- {1} -> {2}{3}", this, info, value, timeStamp); break; case ExecutionStep.EnterState: // 'value' is the state value that we have transitioned FROM : Console.WriteLine("\tEnter :\t{0} -- {1} -> {2}", value, info, this); break; default: break; } } public override string ToString() { return (IsConstant ? Moniker : Value.ToString()); } } public static void Run() { Console.Clear(); // Create a signal source instance (here, a.k.a."remote control") that implements // IObservable<TvOperation> and IObservable<KeyValuePair<TvOperation, DateTime>> : var remote = new SignalSource<TvOperation, DateTime>(); // Create a television state machine instance (automatically set in a default start state), // and make it subscribe to a compatible signal source, such as the remote control, precisely : var tv = new Television().Using(remote); bool done; // Always holds, assuming the call to Using(...) didn't throw an exception (in case of subscription failure) : System.Diagnostics.Debug.Assert(tv != null,"There's a bug somewhere: this message should never be displayed!"); // As commonly done, we can trigger a transition directly on the state machine : tv.MoveNext(TvOperation.Plug, DateTime.Now); // Alternatively, we can also trigger transitions by emitting from the signal source / remote control // that the state machine subscribed to / is an observer of : remote.Emit(TvOperation.SwitchOn, DateTime.Now); remote.Emit(TvOperation.SwitchOff); remote.Emit(TvOperation.SwitchOn); remote.Emit(TvOperation.SwitchOff, DateTime.Now); done = ( tv. MoveNext(TvOperation.Unplug). MoveNext(TvOperation.Dispose) // MoveNext(...) returns null iff tv.IsFinal == true == null ); remote.Emit(TvOperation.Unplug); // Ignored by the state machine thanks to the OnComplete(...) override above Console.WriteLine(); Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done); Console.WriteLine(); Console.WriteLine("Press any key..."); Console.ReadKey(); } } } |
注意:这个例子相当人工,主要是为了演示一些正交特性。使用crtp(请参见:http://en.wikipedia.org/wiki/curioly_-recurring_-template_-pattern)这样一个完整的类来实现状态值域本身应该是有实际需要的。
下面是一个非常简单而且可能更常见的实现用例(使用一个简单的枚举类型作为状态值域),对于相同的状态机和相同的测试用例:
https://code.google.com/p/ysharp/source/browse/trunk/statemachinespoc/watchingtvsample.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Test { using Machines; public static class WatchingTvSample { public enum Status { Unplugged, Off, On, Disposed } public class DeviceTransitionAttribute : TransitionAttribute { public Status From { get; set; } public string When { get; set; } public Status Goto { get; set; } public object With { get; set; } } // State<Status> is a shortcut for / derived from State<Status, string>, // which in turn is a shortcut for / derived from State<Status, string, object> : public class Device : State<Status> { // Executed before and after every state transition : protected override void OnChange(ExecutionStep step, Status value, string info, object args) { if (step == ExecutionStep.EnterState) { // 'value' is the state value that we have transitioned FROM : Console.WriteLine("\t{0} -- {1} -> {2}", value, info, this); } } public override string ToString() { return Value.ToString(); } } // Since 'Device' has no state graph of its own, define one for derived 'Television' : [DeviceTransition(From = Status.Unplugged, When ="Plug", Goto = Status.Off)] [DeviceTransition(From = Status.Unplugged, When ="Dispose", Goto = Status.Disposed)] [DeviceTransition(From = Status.Off, When ="Switch On", Goto = Status.On)] [DeviceTransition(From = Status.Off, When ="Unplug", Goto = Status.Unplugged)] [DeviceTransition(From = Status.Off, When ="Dispose", Goto = Status.Disposed)] [DeviceTransition(From = Status.On, When ="Switch Off", Goto = Status.Off)] [DeviceTransition(From = Status.On, When ="Unplug", Goto = Status.Unplugged)] [DeviceTransition(From = Status.On, When ="Dispose", Goto = Status.Disposed)] public class Television : Device { } public static void Run() { Console.Clear(); // Create a television state machine instance, and return it, set in some start state : var tv = new Television().Start(Status.Unplugged); bool done; // Holds iff the chosen start state isn't a final state : System.Diagnostics.Debug.Assert(tv != null,"The chosen start state is a final state!"); // Trigger some state transitions with no arguments // ('args' is ignored by this state machine's OnChange(...), anyway) : done = ( tv. MoveNext("Plug"). MoveNext("Switch On"). MoveNext("Switch Off"). MoveNext("Switch On"). MoveNext("Switch Off"). MoveNext("Unplug"). MoveNext("Dispose") // MoveNext(...) returns null iff tv.IsFinal == true == null ); Console.WriteLine(); Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done); Console.WriteLine(); Console.WriteLine("Press any key..."); Console.ReadKey(); } } } |
'Hth'
多好的状态模式啊。这符合你的需要吗?
我认为它与环境有关,但它确实值得一试。
http://en.wikipedia.org/wiki/state_模式
这让您的状态决定去哪里,而不是"对象"类。
布鲁诺
我用朱丽叶的代码制作了这个通用状态机。这对我来说太棒了。
这些是好处:
- 您可以用两个枚举
TState 和TCommand 在代码中创建新的状态机, - 增加了结构
TransitionResult 以更好地控制[Try]GetNext() 方法的输出结果 - 只通过
AddTransition(TState, TCommand, TState) 公开嵌套类StateTransition ,使使用它更容易
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | public class StateMachine<TState, TCommand> where TState : struct, IConvertible, IComparable where TCommand : struct, IConvertible, IComparable { protected class StateTransition<TS, TC> where TS : struct, IConvertible, IComparable where TC : struct, IConvertible, IComparable { readonly TS CurrentState; readonly TC Command; public StateTransition(TS currentState, TC command) { if (!typeof(TS).IsEnum || !typeof(TC).IsEnum) { throw new ArgumentException("TS,TC must be an enumerated type"); } CurrentState = currentState; Command = command; } public override int GetHashCode() { return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode(); } public override bool Equals(object obj) { StateTransition<TS, TC> other = obj as StateTransition<TS, TC>; return other != null && this.CurrentState.CompareTo(other.CurrentState) == 0 && this.Command.CompareTo(other.Command) == 0; } } private Dictionary<StateTransition<TState, TCommand>, TState> transitions; public TState CurrentState { get; private set; } protected StateMachine(TState initialState) { if (!typeof(TState).IsEnum || !typeof(TCommand).IsEnum) { throw new ArgumentException("TState,TCommand must be an enumerated type"); } CurrentState = initialState; transitions = new Dictionary<StateTransition<TState, TCommand>, TState>(); } /// <summary> /// Defines a new transition inside this state machine /// </summary> /// <param name="start">source state</param> /// <param name="command">transition condition</param> /// <param name="end">destination state</param> protected void AddTransition(TState start, TCommand command, TState end) { transitions.Add(new StateTransition<TState, TCommand>(start, command), end); } public TransitionResult<TState> TryGetNext(TCommand command) { StateTransition<TState, TCommand> transition = new StateTransition<TState, TCommand>(CurrentState, command); TState nextState; if (transitions.TryGetValue(transition, out nextState)) return new TransitionResult<TState>(nextState, true); else return new TransitionResult<TState>(CurrentState, false); } public TransitionResult<TState> MoveNext(TCommand command) { var result = TryGetNext(command); if(result.IsValid) { //changes state CurrentState = result.NewState; } return result; } } |
这是TryGetNext方法的返回类型:
1 2 3 4 5 6 7 8 9 10 | public struct TransitionResult<TState> { public TransitionResult(TState newState, bool isValid) { NewState = newState; IsValid = isValid; } public TState NewState; public bool IsValid; } |
如何使用:
这是从泛型类创建
为其状态定义枚举
使用这两个枚举定义从泛型类派生的类
从
根据需要多次使用
1 2 3 4 5 6 7 8 9 10 | public class OnlineDiscountStateMachine : StateMachine<OnlineDiscountState, OnlineDiscountCommand> { public OnlineDiscountStateMachine() : base(OnlineDiscountState.Disconnected) { AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Connected); AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Error_AuthenticationError); AddTransition(OnlineDiscountState.Connected, OnlineDiscountCommand.Submit, OnlineDiscountState.WaitingForResponse); AddTransition(OnlineDiscountState.WaitingForResponse, OnlineDiscountCommand.DataReceived, OnlineDiscountState.Disconnected); } } |
使用派生状态机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | odsm = new OnlineDiscountStateMachine(); public void Connect() { var result = odsm.TryGetNext(OnlineDiscountCommand.Connect); //is result valid? if (!result.IsValid) //if this happens you need to add transitions to the state machine //in this case result.NewState is the same as before Console.WriteLine("cannot navigate from this state using OnlineDiscountCommand.Connect"); //the transition was successfull //show messages for new states else if(result.NewState == OnlineDiscountState.Error_AuthenticationError) Console.WriteLine("invalid user/pass"); else if(result.NewState == OnlineDiscountState.Connected) Console.WriteLine("Connected"); else Console.WriteLine("not implemented transition result for" + result.NewState); } |
在我看来,状态机不仅用于改变状态,而且(非常重要)用于处理特定状态中的触发器/事件。如果你想更好地理解状态机设计模式,可以在320页的书头第一设计模式中找到一个很好的描述。
它不仅与变量中的状态有关,还与在不同状态中处理触发器有关。伟大的篇章(不,不,我提到这一点是不收费的),其中包含了一个简单易懂的解释。
我认为Juliet提出的状态机有一个错误:getHashCode方法可以为两个不同的转换返回相同的哈希代码,例如:
State = Active (1) , Command = Pause (2) => HashCode = 17 + 31 + 62 =
110State = Paused (2) , Command = End (1) => HashCode = 17 + 62 + 31 =
110
为避免此错误,方法应如下所示:
1 2 3 4 | public override int GetHashCode() { return 17 + 23 * CurrentState.GetHashCode() + 31 * Command.GetHashCode(); } |
亚历克斯
FiniteStemachine是一个简单的状态机,用C Link编写
使用我的库FiniteStemachine的优势:
下载DLL下载
LinqPad示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | void Main() { var machine = new SFM.Machine(new StatePaused()); var output = machine.Command("Input_Start", Command.Start); Console.WriteLine(Command.Start.ToString() +"-> State:" + machine.Current); Console.WriteLine(output); output = machine.Command("Input_Pause", Command.Pause); Console.WriteLine(Command.Pause.ToString() +"-> State:" + machine.Current); Console.WriteLine(output); Console.WriteLine("-------------------------------------------------"); } public enum Command { Start, Pause, } public class StateActive : SFM.State { public override void Handle(SFM.IContext context) { //Gestione parametri var input = (String)context.Input; context.Output = input; //Gestione Navigazione if ((Command)context.Command == Command.Pause) context.Next = new StatePaused(); if ((Command)context.Command == Command.Start) context.Next = this; } } public class StatePaused : SFM.State { public override void Handle(SFM.IContext context) { //Gestione parametri var input = (String)context.Input; context.Output = input; //Gestione Navigazione if ((Command)context.Command == Command.Start) context.Next = new StateActive(); if ((Command)context.Command == Command.Pause) context.Next = this; } } |
我推荐State.cs.我个人使用了state.js(javascript版本),对此我非常满意。这个C版本以类似的方式工作。
实例化状态:
1 2 3 4 5 6 7 | // create the state machine var player = new StateMachine<State>("player" ); // create some states var initial = player.CreatePseudoState("initial", PseudoStateKind.Initial ); var operational = player.CreateCompositeState("operational" ); ... |
您可以实例化一些转换:
1 2 3 4 | var t0 = player.CreateTransition( initial, operational ); player.CreateTransition( history, stopped ); player.CreateTransition<String>( stopped, running, ( state, command ) => command.Equals("play" ) ); player.CreateTransition<String>( active, stopped, ( state, command ) => command.Equals("stop" ) ); |
定义状态和转换的操作:
1 2 | t0.Effect += DisengageHead; t0.Effect += StopMotor; |
就这样(差不多)。有关更多信息,请访问网站。
Nuget中有两个流行的状态机包。
appccelerate.statemachine(13.6K下载+3.82K旧版(bbv.common.statemachine))
StateMachineToolkit(1.56k下载)
AppCCelerate库有很好的文档,但它不支持.NET 4,因此我为我的项目选择了StateMachineToolKit。