Drücken Sie „Enter“, um den Inhalte zu überspringen

Mono | Zustandsautomat

Die folgende Implementierung stellt die Basisklasse für einen einfachen Zustandsautomaten (State Machine) dar. Der Zustandsautomat wird initialisiert mit einem Startzustand und einer Liste von Zuständen und deren Zustandsübergängen. Vor und nach jedem Zustandsübergang kann optional eine individuelle Methode aufgerufen werden. Zusätzlich verfügt der Zustandsautomat über optionale allgemeine Methoden, die vor und nach jedem Zustandsübergang aufgerufen werden, sowie einer optionalen Methode, die bei einem unzulässigen Zustandsübergang aufgerufen wird. Ein unzulässiger Zustandsübergang führt nicht zu einem Zustandswechsel.

Code

using System;
using System.Collections.Generic;
using System.Linq;

namespace idesis.StateMachine
{
    /// <summary>   A state machine. </summary>
    ///
    /// <remarks>   Mersch, 20.04.2017. </remarks>
    ///
    /// <typeparam name="TState">   Type of the state. </typeparam>
    /// <typeparam name="TCommand"> Type of the command. </typeparam>

    public abstract class StateMachine<TState, TCommand> 
        where TState : struct
        where TCommand : struct
    {
        /// <summary>   A state transition. </summary>
        ///
        /// <remarks>   Mersch, 20.04.2017. </remarks>

        class StateTransition
        {

            /// <summary>   The current state. </summary>
            public readonly TState CurrentState;

            /// <summary>   The command. </summary>
            public readonly TCommand Command;

            /// <summary>   Gets or sets the on before transition. </summary>
            ///
            /// <value> The on before transition. </value>

            public Action OnBeforeTransition { get; set; }

            /// <summary>   Gets or sets the on after transition. </summary>
            ///
            /// <value> The on after transition. </value>

            public Action OnAfterTransition { get; set; }

            /// <summary>   Constructor. </summary>
            ///
            /// <remarks>   Mersch, 20.04.2017. </remarks>
            ///
            /// <exception cref="ArgumentException">    Thrown when one or more arguments have unsupported or
            ///                                         illegal values. </exception>
            ///
            /// <param name="currentState"> The current state. </param>
            /// <param name="command">      The command. </param>

            public StateTransition (TState currentState, TCommand command)
            {
                if (!typeof(TState).IsEnum)
                    throw new ArgumentException ("TState must be an enumerated type");
                if (!typeof(TCommand).IsEnum)
                    throw new ArgumentException ("TCommand must be an enumerated type");

                this.CurrentState = currentState;
                this.Command = command;
            }

            /// <summary>   Calculates a hash code for this object. </summary>
            ///
            /// <remarks>   Mersch, 20.04.2017. </remarks>
            ///
            /// <returns>   A hash code for this object. </returns>

            public override int GetHashCode ()
            {
                return 17 + 31 * CurrentState.GetHashCode () + 31 * Command.GetHashCode ();
            }

            /// <summary>
            /// Determines whether the specified <see cref="T:System.Object" /> is equal to the current
            /// <see cref="T:System.Object" />.
            /// </summary>
            ///
            /// <remarks>   Mersch, 20.04.2017. </remarks>
            ///
            /// <param name="obj">  The object to compare with the current object. </param>
            ///
            /// <returns>
            /// true if the specified object  is equal to the current object; otherwise, false.
            /// </returns>

            public override bool Equals (object obj)
            {
                if (obj is StateTransition)
                    return Equals (obj as StateTransition);

                return false;
            }

            private bool Equals (StateTransition other)
            {
                return this.CurrentState.Equals (other.CurrentState) && this.Command.Equals (other.Command);
            }
        }

        private Dictionary<StateTransition, TState> transitions;

        /// <summary>   Gets or sets the current state. </summary>
        ///
        /// <value> The current state. </value>

        public TState CurrentState { get; private set; }

        /// <summary>   Gets or sets the on invalid transition. </summary>
        ///
        /// <value> The on invalid transition. </value>

        public Action<TState, TCommand> OnInvalidTransition { get; set; }

        /// <summary>   Gets or sets the on before transition. </summary>
        ///
        /// <value> The on before transition. </value>

        public Action<TState, TCommand> OnBeforeTransition { get; set; }

        /// <summary>   Gets or sets the on after transition. </summary>
        ///
        /// <value> The on after transition. </value>

        public Action<TState, TCommand> OnAfterTransition { get; set; }

        /// <summary>   Constructor. </summary>
        ///
        /// <remarks>   Mersch, 20.04.2017. </remarks>
        ///
        /// <param name="initialState"> State of the initial. </param>

        public StateMachine (TState initialState)
        {
            CurrentState = initialState;
            transitions = new Dictionary<StateTransition, TState> ();
        }

        /// <summary>   Move next. </summary>
        ///
        /// <remarks>   Mersch, 20.04.2017. </remarks>
        ///
        /// <exception cref="InvalidTransitionException">   Thrown when an Invalid Transition error
        ///                                                 condition occurs. </exception>
        ///
        /// <param name="command">  The command. </param>
        ///
        /// <returns>   A TState. </returns>

        public TState MoveNext (TCommand command)
        {
            var transition = transitions.Keys.FirstOrDefault (t => t.CurrentState.Equals (CurrentState) && t.Command.Equals (command));
            if (transition == null) {
                if (OnInvalidTransition == null)
                    throw new InvalidTransitionException ("Invalid transition: " + CurrentState + " -> " + command);
                else {
                    OnInvalidTransition (CurrentState, command);
                    return CurrentState;
                }
            }

            transition.OnBeforeTransition?.Invoke();
            OnBeforeTransition?.Invoke(CurrentState, command);

            var nextState = transitions [transition];
            CurrentState = nextState;

            transition.OnAfterTransition?.Invoke();
            OnAfterTransition?.Invoke(CurrentState, command);

            return CurrentState;
        }

        /// <summary>   Adds a transition. </summary>
        ///
        /// <remarks>   Mersch, 20.04.2017. </remarks>
        ///
        /// <param name="startState">       The start state. </param>
        /// <param name="endState">         The end state. </param>
        /// <param name="transition">       The transition. </param>
        /// <param name="beforeTransition"> (Optional) The before transition. </param>
        /// <param name="afterTransition">  (Optional) The after transition. </param>

        protected void AddTransition (TState startState, TState endState, TCommand transition, Action beforeTransition = null, Action afterTransition = null)
        {
            transitions.Add (new StateTransition (startState, transition) {
                OnBeforeTransition = beforeTransition,
                OnAfterTransition = afterTransition
            }, endState);
        }
    }
}

Verwendung

TODO

Share This

Share This

Share this post with your friends!