Programing

C #의 간단한 상태 머신 예제?

lottogame 2020. 4. 9. 07:56
반응형

C #의 간단한 상태 머신 예제?


최신 정보:

예제에 다시 한 번 감사 드리며, 그들은 매우 도움이되었으며 다음과 같이 나는 그들로부터 아무것도 빼앗지 않을 것입니다.

내가 제시 한 예와 상태 머신을 이해하는 한, 상태 머신이 일반적으로 이해하는 것의 절반 만 현재 주어진 예제가 아닙니까?
예제는 상태를 변경하지만 변수의 값을 변경하고 다른 상태에서 다른 값 변경을 허용하는 것으로 만 표현되는 반면, 일반적으로 상태 시스템은 동작을 변경하지 않아야합니다. 상태에 따라 변수에 대해 다른 값 변경을 허용한다는 의미이지만 다른 상태에 대해 다른 방법을 실행할 수 있다는 의미입니다.

아니면 상태 머신과 일반적인 사용법에 대한 오해가 있습니까?

친애하는


원래 질문 :

C #의 상태 시스템 및 반복자 블록 과 상태 시스템을 만드는 도구 및 C #을위한 도구 대한 토론을 찾았 으므로 많은 추상적 인 내용을 찾았지만 멍청한 놈은 모두 약간 혼란 스럽습니다.

따라서 누군가가 3,4 개의 상태로 간단한 상태 머신을 실현하는 C # 소스 코드 예제를 제공 할 수 있다면 좋을 것입니다.



이 간단한 상태 다이어그램으로 시작하겠습니다.

간단한 상태 머신 다이어그램

우리는 :

  • 4 가지 상태 (비활성, 활성, 일시 중지 및 종료)
  • 5 가지 유형의 상태 전환 (시작 명령, 종료 명령, 일시 중지 명령, 재개 명령, 종료 명령).

현재 상태 및 명령에 대해 switch 문을 수행하거나 전이 테이블에서 전이를 찾는 등의 몇 가지 방법으로이를 C #으로 변환 할 수 있습니다. 이 간단한 상태 머신의 경우 다음을 사용하여 표현하기 매우 쉬운 전이 테이블을 선호합니다 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다음 상태를 반환하는 기능을 결정적으로 하고, MoveNext상태 머신을 변이하는 기능.


기존 오픈 소스 Finite State Machine 중 하나를 사용할 수 있습니다. 예를 들어 bbv.Common.StateMachine은 http://code.google.com/p/bbvcommon/wiki/StateMachine에 있습니다. 매우 직관적 인 유창한 구문과 진입 / 종료 동작, 전환 동작, 가드, 계층 적, 수동적 구현 (호출자의 스레드에서 실행) 및 활성 구현 (FSM이 실행되는 자체 스레드, 이벤트가 대기열에 추가됩니다).

Juliets를 예로 들어 상태 머신에 대한 정의는 매우 쉽습니다.

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으로 이동했습니다.


다음은 매우 고전적인 유한 상태 기계의 예입니다 (TV와 같은 매우 단순화 된 전자 장치 모델링).

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 코드에서는 모든 상태 관련 동작에 대해 단일 메소드를 작성합니다 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

        ...

이것은 C # 컴파일러가 실제로를 사용하는 각 메소드에 대해 내부적으로 상태 머신을 작성했기 때문에 작동합니다 yield return. 이 구문은 일반적으로 데이터 시퀀스를 느리게 생성하는 데 사용되지만이 경우 실제로 반환 된 시퀀스 (모두 null 임)에 관심이 없지만 후드 아래에서 생성되는 상태 동작에 실제로 관심이 있습니다.

StateMachine기본 클래스는 각각 할당 코드 구조에 일부 반사하지 [Trigger]세트 작용 Trigger부재를 전진 상태 머신을 이동.

그러나 당신은 그것을 사용하기 위해 내부를 이해할 필요가 없습니다.


코드 블록을 오케스트레이션 방식으로 실행할 수있는 반복자 블록을 코딩 할 수 있습니다. 코드 블록이 어떻게 분해되는지는 실제로 어떤 것에도 대응할 필요가 없으며, 단지 코딩하려는 방식입니다. 예를 들면 다음과 같습니다.

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가 표시됩니다. 보시다시피, 일이 어떻게 발생해야 하는지를 조정하는 데 유용한 메커니즘입니다.


이것은 다른 관점에서 상태 머신이기 때문에 여기에 또 다른 대답을 게시합니다. 매우 시각적입니다.

내 origianl 답변은 고전적 명령입니다. 코드가 배열을 대신하여 시각적 인 상태를 시각화하는 것이 상당히 시각적이라고 생각합니다. 단점은이 모든 것을 작성해야한다는 것입니다. Remos 의 대답은 보일러 플레이트 코드를 작성하는 노력을 줄여 주지만 훨씬 덜 시각적입니다. 세 번째 대안이 있습니다. 실제로 상태 머신을 그립니다.

.NET을 사용하고 런타임 버전 4를 대상으로 할 수있는 경우 워크 플로우의 상태 머신 활동 을 사용하는 옵션이 있습니다. 이것들은 본질적으로 상태 머신 ( 줄리엣 의 다이어그램 에서와 같이 )을 그리고 WF 런타임이 당신을 위해 그것을 실행하게합니다.

자세한 내용은 MSDN 문서 Windows Workflow Foundation 을 사용 하여 State Machines 빌드최신 버전에 대한 CodePlex 사이트 를 참조하십시오.

.NET을 타겟팅 할 때 내가 선호하는 옵션은 프로그래머가 아닌 사람에게는 쉽게보고 변경하고 설명 할 수 있기 때문입니다. 그들이 말하는대로 그림은 천 단어의 가치가있다!


상태 머신은 추상화이며,이를 생성하기 위해 특정 도구가 필요하지 않지만 도구가 유용 할 수 있음을 기억하는 것이 유용합니다.

예를 들어 함수가있는 상태 머신을 실현할 수 있습니다.

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);
}

이 기계는 갈매기를 사냥하고 물 풍선으로 때리려 고합니다. 만약 놓치면 맞을 때까지 발사를 시도합니다 (실제로 기대할 수 있습니다;)) 그렇지 않으면 콘솔에서 화를냅니다. 갈매기가 괴롭힐 때까지 계속 사냥합니다.

각 기능은 각 상태에 해당합니다. 시작 및 종료 (또는 수락 ) 상태는 표시되지 않습니다. 함수에 의해 모델링 된 것보다 더 많은 상태가있을 수 있습니다. 예를 들어 풍선을 발사 한 후 기계는 실제로 이전과 다른 상태에 있지만이 구별은 비실용적이라고 결정했습니다.

일반적인 방법은 클래스를 사용하여 상태를 표현한 다음 다른 방식으로 연결하는 것입니다.


이 훌륭한 튜토리얼을 온라인에서 찾았으며 유한 상태 머신을 둘러 보았습니다.

http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867

이 자습서는 언어에 구애받지 않으므로 C # 요구에 쉽게 맞출 수 있습니다.

또한 사용 된 예 (음식을 찾는 개미)는 이해하기 쉽습니다.


튜토리얼에서 :

여기에 이미지 설명을 입력하십시오

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 #의 스레딩 그림에 설명 된 것처럼 C #의 스레딩과 동일한 (+/-) ThreadState를 수행하고 테스트 했습니다.

여기에 이미지 설명을 입력하십시오

새로운 상태를 쉽게 추가하고 한 상태에서 다른 상태로 이동을 구성 할 수 있으므로 상태 구현에 캡슐화되어 있으므로 매우 쉽습니다.

구현 및 사용 : 상태 디자인 패턴에 따라 .NET ThreadState 구현


나는 C #으로 FSM을 구현하려고 시도하지 않았지만, 이러한 모든 소리 (또는 모양)는 과거에 C 또는 ASM과 같은 저수준 언어에서 FSM을 처리하는 방식에 매우 복잡합니다.

내가 항상 알고있는 방법을 "반복 루프"라고합니다. 여기에는 본질적으로 이벤트 (인터럽트)를 기반으로 주기적으로 종료 한 다음 메인 루프로 다시 돌아가는 'while'루프가 있습니다.

인터럽트 처리기 내에서 CurrentState를 전달하고 NextState를 반환하면 주 루프의 CurrentState 변수를 덮어 씁니다. 프로그램이 닫힐 때까지 (또는 마이크로 컨트롤러가 재설정 될 때까지)이 작업을 무한정 수행합니다.

다른 답변을보고있는 것은 FSM이 내 생각에 구현되는 방식과 비교할 때 매우 복잡해 보입니다. 그것의 아름다움은 단순함에 있으며, FSM은 많은, 많은 상태와 전환으로 인해 매우 복잡 할 수 있지만 복잡한 프로세스는 쉽게 분해되고 소화 될 수 있습니다.

내 답변에 다른 질문이 포함되어서는 안된다는 것을 알고 있지만, 이러한 다른 제안 된 솔루션이 왜 그렇게 복잡해 보이는가?
그들은 거대한 썰매 망치로 작은 못을 치는 것과 비슷해 보입니다.


한판 승부 상태입니다. 그것이 당신의 필요에 맞습니까?

나는 그 맥락과 관련이 있다고 생각하지만 확실히 가치가 있습니다.

http://en.wikipedia.org/wiki/State_pattern

이를 통해 주에서는 "개체"클래스가 아닌 어디로 가야할지 결정할 수 있습니다.

브루노


방금 이것에 기여했습니다.

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'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/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'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


Juliet의 코드 에서이 일반 상태 머신을 만들었습니다 . 그것은 나를 위해 굉장히 노력하고 있습니다.

다음과 같은 이점이 있습니다.

  • 두 열거와 코드에서 새로운 상태 머신을 생성 할 수 있습니다 TStateTCommand,
  • 메소드 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일반 클래스에서를 만드는 방법입니다 .

OnlineDiscountState상태에 대한 열거 형과 OnlineDiscountCommand명령에 대한 열거 정의하십시오 .

OnlineDiscountStateMachine이 두 열거 형을 사용하여 일반 클래스에서 파생 된 클래스를 정의하십시오.

초기 상태 가로 설정 base(OnlineDiscountState.InitialState)되도록 생성자를 파생시킵니다.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);
    }

제 생각에는 상태 머신은 상태를 변경하는 것뿐만 아니라 특정 상태 내에서 트리거 / 이벤트를 처리하기 위해 (매우 중요) 의미합니다. 상태 머신 디자인 패턴을 더 잘 이해하려면 320 페이지 헤드 우선 디자인 패턴 책에서 자세한 설명을 찾을 수 있습니다 .

변수 내의 상태뿐만 아니라 다른 상태 내의 트리거 처리에 관한 것입니다. 이해하기 쉬운 설명이 들어있는 훌륭한 장 (그리고 아니오, 이것을 언급하는 데 비용이 들지 않습니다 :-).


Juliet가 제안한 상태 시스템은 실수가 있다고 생각합니다. GetHashCode 메소드 는 두 가지 다른 전이에 대해 동일한 해시 코드를 리턴 할 수 있습니다. 예를 들면 다음과 같습니다.

상태 = 활성 (1), 명령 = 일시 중지 (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();
   }

알렉스


FiniteStateMachine은 C # 링크로 작성된 단순 상태 머신입니다.

내 라이브러리 FiniteStateMachine을 사용할 때의 이점 :

  1. "컨텍스트"클래스를 정의하여 외부 세계에 단일 인터페이스를 제공하십시오.
  2. 상태 추상 기본 클래스를 정의하십시오.
  3. 상태 머신의 다른 "상태"를 State 기본 클래스의 파생 클래스로 나타냅니다.
  4. 적절한 State 파생 클래스에서 상태 별 동작을 정의하십시오.
  5. "컨텍스트"클래스에서 현재 "상태"에 대한 포인터를 유지하십시오.
  6. 상태 머신의 상태를 변경하려면 현재 "상태"포인터를 변경하십시오.

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;


     }

 }

state.cs 권장 합니다 . 개인적으로 state.js (JavaScript 버전)를 사용했으며 매우 만족합니다. 그 C # 버전도 비슷한 방식으로 작동합니다.

상태를 인스턴스화합니다.

        // 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;

그리고 그것은 (거의) 그것입니다. 자세한 내용은 웹 사이트를 참조하십시오.


NuGet에는 2 개의 인기있는 상태 머신 패키지가 있습니다.

Appccelerate.StateMachine (13.6K 다운로드 + 3.82K의 레거시 버전 (bbv.Common.StateMachine))

StateMachineToolkit (1.56K 다운로드)

Appccelerate lib에는 좋은 문서 가 있지만 .NET 4를 지원하지 않으므로 프로젝트에 StateMachineToolkit을 선택했습니다.


이 저장소의 다른 대안 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
    }
}

참고 URL : https://stackoverflow.com/questions/5923767/simple-state-machine-example-in-c

반응형