Простой пример состояния машины в С#?
Обновление:
Снова спасибо за примеры, они были очень полезны, и со следующим я не имею в виду
взять что-нибудь от них.
Не представлены ли в настоящее время примеры, насколько я понимаю их и государственные машины, только половину того, что мы обычно понимаем государственной машиной?
В том смысле, что примеры изменяют состояние, но это только представлено изменением значения переменной (и допускающей разные изменения значений в разных состояниях), в то время как обычно машина состояния также должна изменять ее поведение, а поведение не (только) в смысл допускать изменение разных значений для переменной в зависимости от состояния, но в смысле разрешения различных методов для разных состояний.
Или у меня есть неправильное представление о государственных машинах и их общее использование?
С наилучшими пожеланиями
Оригинальный вопрос:
Я нашел это обсуждение состояний машин и блоков итератора в С# и инструментов для создания государственных машин, а что не для С#, поэтому я нашел много абстрактных материалов, но как noob, все это немного запутывает.
Итак, было бы здорово, если бы кто-то мог представить пример исходного кода на С#, который реализует простой конечный автомат с возможно 3,4 состояниями, чтобы получить его суть.
Ответы
Ответ 1
Начнем с этой простой диаграммы состояний:
![простая диаграмма состояний машины]()
Имеем:
- 4 состояния (неактивные, активные, приостановленные и завершенные)
- 5 типов переходов состояний (команда начала, команда завершения, команда приостановки, команда возобновления, команда выхода).
Вы можете преобразовать это в С# несколькими способами, такими как выполнение оператора switch в текущем состоянии и команде или поиск переходов в таблице переходов. Для этого простого конечного автомата я предпочитаю таблицу переходов, которую очень легко представить с помощью Dictionary
:
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();
}
}
}
В качестве личного предпочтения мне нравится создавать мои государственные машины с помощью функции GetNext
для возврата следующего состояния детерминистически и a MoveNext
, чтобы мутировать конечный автомат.
Ответ 2
Возможно, вы захотите использовать один из существующих конечных автоматов с открытым исходным кодом. Например. bbv.Common.StateMachine найден в http://code.google.com/p/bbvcommon/wiki/StateMachine. Он имеет очень интуитивно понятный свободный синтаксис и множество функций, таких как действия входа/выхода, действия перехода, охранники, иерархическая, пассивная реализация (выполняется в потоке вызывающей стороны) и активная реализация (собственный поток, в котором работает fsm, события добавляются в очередь).
На примере Джульетта определение конечного автомата становится очень простым:
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
Ответ 3
Вот пример очень классического конечного автомата, моделирующего очень упрощенное электронное устройство (например, телевизор)
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; }
}
}
}
Ответ 4
Некоторое бесстыдное самообеспечение здесь, но некоторое время назад я создал библиотеку под названием YieldMachine, которая позволяет конечному компьютеру с ограниченной сложностью описываются очень простым и простым способом. Например, рассмотрите лампу:
![state machine of a lamp]()
Обратите внимание, что этот конечный автомат имеет 2 триггера и 3 состояния. В коде YieldMachine мы пишем один метод для всех состояний, связанных с поведением, в которых мы совершаем ужасную зверство использования goto
для каждого состояния. Триггер становится свойством или полем типа Action
, украшенным атрибутом Trigger
. Я прокомментировал код первого состояния и его переходы ниже; следующие состояния следуют одному и тому же шаблону.
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();
}
}
Короткий и приятный, а!
Этот конечный автомат управляется просто путем отправки ему триггеров:
var sm = new Lamp();
sm.PressSwitch(); //go on
sm.PressSwitch(); //go off
sm.PressSwitch(); //go on
sm.GotError(); //get error
sm.PressSwitch(); //go off
Чтобы уточнить, я добавил некоторые комментарии к первому состоянию, чтобы помочь вам понять, как это использовать.
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
...
Это работает, потому что компилятор С# фактически создал конечный автомат для каждого метода, который использует yield return
. Эта конструкция обычно используется для ленивого создания последовательностей данных, но в этом случае мы фактически не заинтересованы в возвращенной последовательности (которая все равно равна нулю), а в поведении состояния, которое создается под капотом.
Базовый класс StateMachine
делает некоторое размышление о конструкции для назначения кода для каждого действия [Trigger]
, которое устанавливает член Trigger
и перемещает машину состояния вперед.
Но вам не нужно понимать внутренности, чтобы иметь возможность использовать его.
Ответ 5
Вы можете запрограммировать блок итератора, который позволяет выполнять блок кода в организованном порядке. Как разбить блок кода действительно не нужно что-либо соответствовать, просто как вы хотите его закодировать. Например:
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. Как вы можете видеть, это полезный механизм для организации того, как должно происходить.
Ответ 6
Я публикую здесь другой ответ, так как это конечные автоматы с другой точки зрения; очень наглядно.
Мой оригинальный ответ - классический неуязвимый код. Я думаю, что это довольно наглядно, поскольку код идет из-за массива, который делает визуализацию конечного автомата простой. Недостатком является то, что вы должны написать все это. Ответ Remos облегчает написание кода, но гораздо менее нагляден. Есть третья альтернатива; действительно рисовать конечный автомат.
Если вы используете .NET и можете использовать версию 4 среды выполнения, у вас есть возможность использовать действия конечного автомата рабочего процесса. По сути, это позволяет вам нарисовать конечный автомат (как на диаграмме Джульетты) и заставить среду выполнения WF выполнить его за вас.
Дополнительную информацию см. в статье MSDN Создание конечных автоматов с помощью Windows Workflow Foundation и этого сайта CodePlex для получения последней версии.
Это вариант, который я бы всегда предпочел при ориентации на .NET, потому что его легко увидеть, изменить и объяснить не программистам; картинки стоят тысячи слов как говорится!
Ответ 7
Полезно помнить, что государственные машины являются абстракцией, и вам не нужны специальные инструменты для ее создания, однако инструменты могут быть полезны.
Вы можете, например, реализовать конечный автомат с функциями:
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);
}
Эта машина будет охотиться за чаек и попытаться ударить их воздушными шарами. Если он промахивается, он попытается выстрелить один, пока он не достигнет (может сделать с некоторыми реалистичными ожиданиями;)), иначе он будет злорадствовать в консоли. Он продолжает охотиться до тех пор, пока из чаек не будет издеваться.
Каждая функция соответствует каждому состоянию; состояния начала и конца (или принятия) не отображаются. Там, вероятно, больше состояний, чем моделируются функциями. Например, после запуска шара машина действительно находится в другом состоянии, чем раньше, но я решил, что это различие нецелесообразно.
Общим способом является использование классов для представления состояний, а затем их соединение по-разному.
Ответ 8
Сегодня я в глубине государства Design Pattern.
Я сделал и протестировал ThreadState, который равен (+ / -) Threading в С#, как описано на рисунке из Threading в С#
![enter image description here]()
Вы можете легко добавлять новые состояния, настраивать переходы из одного состояния в другое очень легко, потому что оно включено в реализацию состояний
Реализация и использование в: Реализация .NET ThreadState по шаблону State State
Ответ 9
Нашел этот отличный учебник в Интернете, и он помог мне обернуть голову в конечные автоматы.
http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867
Учебное пособие не зависит от языка, поэтому его можно легко адаптировать к вашим потребностям в С#.
Кроме того, используемый пример (муравей ищет еду) легко понять.
Из учебника:
![enter image description here]()
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 time
// to go home.
brain.setState(goHome);
}
if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
// Mouse cursor is threatening us. Let 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 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 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();
}
(...)
}
Ответ 10
Я еще не пробовал реализовать FSM на С#, но все эти звуки (или посмотрите) очень сложны для того, как я работал с FSM в прошлом на языках низкого уровня, таких как C или ASM.
Я считаю, что метод, который я всегда знал, называется чем-то вроде "Итеративной петли". В нем у вас по существу есть цикл while, который периодически выходит на основе событий (прерываний), а затем снова возвращается к основному циклу.
В обработчиках прерываний вы должны передать CurrentState и вернуть NextState, которое затем перезаписывает переменную CurrentState в основном цикле. Вы делаете это до бесконечности до тех пор, пока программа не закроется (или сбрасывается микроконтроллер).
То, что я вижу в других ответах, выглядит очень сложным по сравнению с тем, как FSM, по моему мнению, должен быть реализован; его красота заключается в его простоте, и FSM может быть очень сложным со многими, многими состояниями и переходами, но они позволяют легко разлагать и переваривать сложный процесс.
Я понимаю, что мой ответ не должен включать другой вопрос, но я вынужден спросить: почему эти другие предлагаемые решения кажутся настолько сложными?
Они, похоже, сродни удару маленького гвоздя гигантским кувалдом.
Ответ 11
Какой бой StatePattern. Соответствует ли это вашим потребностям?
Я думаю, что его контекст связан, но его стоит наверняка сделать.
http://en.wikipedia.org/wiki/State_pattern
Это позволяет вашим штатам решать, куда идти, а не классу "объект".
Бруно
Ответ 12
Я только что внесли свой вклад:
https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC
Здесь один из примеров демонстрации прямой и косвенной отправки команд с состояниями как IObserver (сигнала), поэтому реагирует на источник сигнала IObservable (сигнала):
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 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 state '{0}' a final state? {1}", tv.Value, done);
Console.WriteLine();
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
}
Примечание. Этот пример довольно искусственен и в основном предназначен для демонстрации ряда ортогональных функций. Редко необходимо реализовать реальную область состояния ценности с помощью полномасштабного класса, используя CRTP (см. http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) как это.
Здесь для более простого и вероятного гораздо более распространенного случая использования использования (с использованием простого типа перечисления как домена значений состояний) для одного и того же конечного автомата и с тем же тестовым случаем:
https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs
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 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 state '{0}' a final state? {1}", tv.Value, done);
Console.WriteLine();
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
}
'НТН
Ответ 13
Я сделал этот универсальный конечный автомат из кода Джульетты. Это работает потрясающе для меня.
Вот эти преимущества:
- Вы можете создать новый конечный автомат в коде с двумя перечислениями
TState
и TCommand
,
- добавлена структура
TransitionResult<TState>
для большего контроля над результатами вывода методов [Try]GetNext()
- показывать вложенный класс
StateTransition
только - AddTransition(TState, TCommand, TState)
, упрощая работу с ним
Код:
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:
public struct TransitionResult<TState>
{
public TransitionResult(TState newState, bool isValid)
{
NewState = newState;
IsValid = isValid;
}
public TState NewState;
public bool IsValid;
}
Как использовать:
Вот как вы можете создать OnlineDiscountStateMachine
из универсального класса:
Определите enum OnlineDiscountState
для его состояний и enum OnlineDiscountCommand
для его команд.
Определите класс OnlineDiscountStateMachine
, производный от универсального класса, используя эти два перечисления
Извлеките конструктор из base(OnlineDiscountState.InitialState)
, чтобы начальное состояние initial state было установлено на OnlineDiscountState.InitialState
Используйте AddTransition
столько раз, сколько необходимо
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);
}
}
использовать производный конечный автомат
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);
}
Ответ 14
По-моему, конечный автомат предназначен не только для изменения состояний, но также (очень важный) для обработки триггеров/событий в определенном состоянии. Если вы хотите лучше понять структуру шаблона государственного аппарата, хорошее описание можно найти в книге Head First Design Patterns, страница 320.
Речь идет не только о состояниях внутри переменных, но и об обработке триггеров в разных состояниях. Великая глава (и нет, нет никакой платы за меня, говоря об этом:-), который содержит только легкое для понимания объяснение.
Ответ 15
Я думаю, что автомат, предложенный Джульеттой, имеет ошибку: метод GetHashCode может возвращать один и тот же хэш-код для двух разных переходов, например:
State = Active (1), Command = Пауза (2) = > HashCode = 17 + 31 + 62 = 110
State = Paused (2), Command = End (1) = > HashCode = 17 + 62 + 31 = 110
Чтобы избежать этой ошибки, метод должен выглядеть следующим образом:
public override int GetHashCode()
{
return 17 + 23 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
}
Алекс
Ответ 16
FiniteStateMachine - это простая машина состояния, написанная на С# Ссылка
Преимущества использования моей библиотеки FiniteStateMachine:
- Определите класс "context", чтобы представить один интерфейс для внешнего мира.
- Определить базовый класс абстрактного состояния.
- Представлять различные "состояния" конечного автомата как производные классы базового класса состояний.
- Определить поведение, специфичное для состояния, в соответствующих классах, связанных с состоянием.
- Сохранять указатель на текущее "состояние" в классе "context".
- Чтобы изменить состояние конечного автомата, измените текущий указатель состояния.
Загрузить DLL Загрузить
Пример для LINQPad:
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;
}
}
Ответ 17
Я бы рекомендовал state.cs. Я лично использовал state.js(версия JavaScript) и очень доволен этим. Эта версия С# работает аналогичным образом.
Вы создаете состояния:
// 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" );
...
Вы создаете несколько переходов:
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" ) );
Вы определяете действия по состояниям и переходам:
t0.Effect += DisengageHead;
t0.Effect += StopMotor;
И это (в значительной степени) это. Посмотрите на сайт для получения дополнительной информации.
Ответ 18
В NuGet есть 2 популярных пакета государственных пакетов.
Appccelerate.StateMachine (13,6 тыс. загрузок + 3,82 тыс. устаревшей версии (bbv.Common.StateMachine))
StateMachineToolkit (загрузка 1.56K)
В Appccelerate lib есть хорошая документация, но она не поддерживает .NET 4, поэтому я выбрал StateMachineToolkit для моего проекта.
Ответ 19
Другая альтернатива в этом репо https://github.com/lingkodsoft/StateBliss
используется свободный синтаксис, поддерживает триггеры.
public class BasicTests
{
[Fact]
public void Tests()
{
// Arrange
StateMachineManager.Register(new [] { typeof(BasicTests).Assembly }); //Register at bootstrap of your application, i.e. Startup
var currentState = AuthenticationState.Unauthenticated;
var nextState = AuthenticationState.Authenticated;
var data = new Dictionary<string, object>();
// Act
var changeInfo = StateMachineManager.Trigger(currentState, nextState, data);
// Assert
Assert.True(changeInfo.StateChangedSucceeded);
Assert.Equal("ChangingHandler1", changeInfo.Data["key1"]);
Assert.Equal("ChangingHandler2", changeInfo.Data["key2"]);
}
//this class gets regitered automatically by calling StateMachineManager.Register
public class AuthenticationStateDefinition : StateDefinition<AuthenticationState>
{
public override void Define(IStateFromBuilder<AuthenticationState> builder)
{
builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
.Changing(this, a => a.ChangingHandler1)
.Changed(this, a => a.ChangedHandler1);
builder.OnEntering(AuthenticationState.Authenticated, this, a => a.OnEnteringHandler1);
builder.OnEntered(AuthenticationState.Authenticated, this, a => a.OnEnteredHandler1);
builder.OnExiting(AuthenticationState.Unauthenticated, this, a => a.OnExitingHandler1);
builder.OnExited(AuthenticationState.Authenticated, this, a => a.OnExitedHandler1);
builder.OnEditing(AuthenticationState.Authenticated, this, a => a.OnEditingHandler1);
builder.OnEdited(AuthenticationState.Authenticated, this, a => a.OnEditedHandler1);
builder.ThrowExceptionWhenDiscontinued = true;
}
private void ChangingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
{
var data = changeinfo.DataAs<Dictionary<string, object>>();
data["key1"] = "ChangingHandler1";
}
private void OnEnteringHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
{
// changeinfo.Continue = false; //this will prevent changing the state
}
private void OnEditedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
{
}
private void OnExitedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
{
}
private void OnEnteredHandler1(StateChangeInfo<AuthenticationState> changeinfo)
{
}
private void OnEditingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
{
}
private void OnExitingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
{
}
private void ChangedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
{
}
}
public class AnotherAuthenticationStateDefinition : StateDefinition<AuthenticationState>
{
public override void Define(IStateFromBuilder<AuthenticationState> builder)
{
builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
.Changing(this, a => a.ChangingHandler2);
}
private void ChangingHandler2(StateChangeGuardInfo<AuthenticationState> changeinfo)
{
var data = changeinfo.DataAs<Dictionary<string, object>>();
data["key2"] = "ChangingHandler2";
}
}
}
public enum AuthenticationState
{
Unauthenticated,
Authenticated
}
}