State Pattern C# with previous states
Asked Answered
N

3

9

I am new to the state pattern implementation in C#, could you provide some info on how you implement it.

I am refactoring a state machine in C# using the state pattern. Currently my state machine contains 5 states and it is only possible to go forward or backward througout the states,i.e. from state 1 you need to go to state 2, 3 and 4 to finally arrive to state 5. enter image description here

I am able to go forward just doing

       mainclass.State = new NextSate();

which creates a new state every time you want to go forward, however, once all of them have been created and/or you want to go backward I would need to go to the same states, not just a new one. How can I do that? Is there any better way to do it simple?

Nahshun answered 24/10, 2011 at 10:10 Comment(2)
Simply store each state in internal stack then when you want to go back pop the stack and assign it to the current stateConquest
Do transitions exist backward from state 1 or forward from state 5? Are these invalid transitions that throw an exception or do you want the state transitions to form a closed loop (i.e. forward from state 5 takes you to state 1, backward from state 1 takes you to state 5)?Foliose
F
12

Strictly speaking, if you're implementing the classic GoF State pattern then the State subclasses themselves are responsible for knowing about and performing the State transitions. The holder of the State isn't responsible for managing the transitions and a large part of the intent of the pattern is to encapsulate the state transition behaviour in the State objects and thus for the client to delegate to them. I've introduced a Factory that ensures that there is only ever a single instance of each State subclass to ensure that the same instance is reused when moving back and forth through the states.

public abstract class State
{
   protected StateFactory _factory;
   protected IStateUser _context;

   public State(StateFactory factory, IStateUser context)
   {
      _factory = factory;
      _context = context;
   }

   protected void TransitionTo<T>(Func<T> creator) where T : State
   {
       State state = _factory.GetOrCreate<T>(creator);
       _context.CurrentState = state;
   }

   public abstract void MoveNext();
   public abstract void MovePrevious();
}

public class State1 : State
{
   public State1(StateFactory factory, IStateUser context)
            : base(factory, context)
   {
   }

   public override void MoveNext()
   {
      TransitionTo<State2>(() => new State2(_factory, _context));
   }

   public override void MovePrevious()
   {
      throw new InvalidOperationException();
   }
}

public class State2 : State
{
   public State2(StateFactory factory, IStateUser context)
            : base(factory, context)
   {
   }

   public override void MoveNext()
   {
      TransitionTo<State3>(() => new State3(_factory, _context)); //State 3 is omitted for brevity
   }

   public override void MovePrevious()
   {
      TransitionTo<State1>(() => new State1(_factory, _context));
   }
}

public interface IStateUser
{
   State CurrentState { get; set; }
}

public class Client : IStateUser
{

   public Client()
   {
      var factory = new StateFactory();
      var first = new State1(factory, this);
      CurrentState = factory.GetOrCreate<State1>(() => first);
   }

   public void MethodThatCausesTransitionToNextState()
   {
      CurrentState.MoveNext();
   }

   public void MethodThatCausesTransitionToPreviousState()
   {
      CurrentState.MovePrevious();
   }

   public State CurrentState
   {
      get;
      set;
   }
}

public class StateFactory
{
    private Dictionary<string, State> _states = new Dictionary<string, State>();

    public State GetOrCreate<T>(Func<T> creator) where T : State
    {
        string typeName = typeof(T).FullName;

        if (_states.ContainsKey(typeName))
            return _states[typeName];

        T state = creator();
        _states.Add(typeName, state);

        return state;
    }
}
Foliose answered 24/10, 2011 at 13:43 Comment(0)
C
11

Use internal stack to maintain the previous states:

public class MyClass
{
  private Stack<State> _states;

  private State _currentState;

  public void GoToNextState()
  {
    // If Not last state then
    _states.Push(_currentState);
    _currentState = new NextState();
  }

  public void GoToPrevState()
  {
    // if not the first state
    _currentState = _states.Pop();
   }
}

if you want to maintain forward and backward states then create additional stack:

public class MyClass
{
    private readonly Stack<State> _nextStates = new Stack<State>();
    private readonly Stack<State> _prevStates = new Stack<State>();

    private State _currentState = new SampleState1();

    public State CurrentState { get { return _currentState; } }

    public void GoToNextState()
    {
        if (_currentState.NextState == null)
            return;

        _prevStates.Push(_currentState);

        _currentState = _nextStates.Count > 0 ? _nextStates.Pop() : _currentState.NextState;
    }

    public void GoToPrevState()
    {
        // if not the first state

        _nextStates.Push(_currentState);
        _currentState = _prevStates.Pop();
    }
}
Conquest answered 24/10, 2011 at 10:15 Comment(6)
I'm not certain that this satisfies the OP's requirements because going back to previous states discards the subsequent ones, so if you go back twice and then forward twice you would create a new state instance for a step which should have already been created.Foliose
@ Steve Rowbotham What do you suggest then Steve? By the way, Thanks a lot @Mohamed Abed.Nahshun
Yes but that should be true according to the original questions's way of handling next new states, as he didnt mention wanting to maintain a forward stateConquest
You can add another stack (one for next states and one for prev. states), then when you move next you check the nextstates stack if empty then create a new state, when you go back pop the prev state and so forthConquest
My observation was based on the part of the OP's post that says "once all of them have been created and/or you want to go backward I would need to go to the same states, not just a new one". So returning to prior states seems only part of it, at least as originally stated.Foliose
added another version which will handle forward and backwardConquest
L
0

Do you have a state manager of some kind? If so, that one could hold the state instances. By decoupling the state transition knowledge from the states themselves, you let the manager decide on the transition. The manager will inspect the state that requested the transition: it determines it is the "step 1" state, and returns (or creates) the "state 2" state.

Lazy answered 24/10, 2011 at 10:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.