0

I have a menu that can be in different states so I decided to use the state pattern. Reading online about the pattern seems like the usual way is to construct states from another state but this will not work for me because I have more complex states which need other dependencies. I could pre-create all states in a context object and then switch states from the context object, but I also need some info from the previous state so it becomes messy too.

     class State1 {
         public State1(Context context, /*some complex constructor*/) {
         }
         public void Action() {
            //Something happend and we want to transition to other state
               
            //Usual way 
            //this will not work because state1 doesnt have 
            //dependencies to construct state2
            //context->SetState(new State2());
            

            //My current way
            //This is better because it works, but you will have to create
            //such functions for every state and context object needs to be 
            //modified if new states are added which is not ideal
            context->SetState2(/*something produced by action in state1*/);
         }
     }   

    class State2 {
         public State2(Context context, /*some complex constructor*/) {
         }
     }   
     
     
    class Context {
      //has current state 
    }

Is this a sign of a bad design?
Is there a clean solution to this or I thinking too much about this?

Argus Kos
  • 175
  • 1
  • 12

1 Answers1

1

An important detail missing from the code in the OP is that states in the State design pattern are polymorphic (there is a shared API across all states).

class State1 implements State {}
class State2 implements State {}
class Context { private volatile State currentState; }

In the absence of polymorphic states, there is no State design pattern. Instantiating the states should be handled in the same manner as instantiating any other business objects in an OO application, i.e. according to the Dependency Inversion Principle. With those things in mind... there are different ways to transition between states in the State pattern.

One way is to manage transitions within the Context. In this scenario, States are decoupled from each other: a State does not know that other States exist. The Context initiates a transition (if necessary) after the handle() (or action()) method returns. This does not necessarily mean the Context is aware of concrete State implementations. The Context may simply have a List of State objects, and transition from one to the next.

Another way is to manage transitions by linking each State to its successor: each State takes another (abstract) State in its constructor. When it wants to transition, it sets the successor on the Context. In this scenario, the Context is unaware of the transition logic, and does not know how many States are available.

jaco0646
  • 15,303
  • 7
  • 59
  • 83
  • Thanks for mentioning that states have to be polymorphic with shared interface I know that but was a bit lazy. I think both of your suggestions will only work with a linear change of states. But imagine the case where we have 3 states. State1 can transition to State2 or State3. So your list of states example is not working here. Your second is example is better, but once again imagine situations where states can switch back and forth. So State1 will need State2 in its constructor and State2 will need State1. Is there some elegant solution for this? – Argus Kos May 27 '21 at 18:30
  • One State can accept any number of additional States in its constructor, for example, `public State2(State predecessor, State successor)`. The same applies to the `Context`. – jaco0646 May 27 '21 at 19:13
  • I understand it, the problem is in circular dependency of states if State1 needs State2 and State2 needs State1 you can't construct ether. The problem also discussed here https://softwareengineering.stackexchange.com/questions/331335/trouble-with-circular-dependency-in-state-machine-design But accepted answer proposed constructing new States from State. Anyway, thank you for the help maybe the state pattern isn't really suitable and I will think some more. – Argus Kos May 28 '21 at 09:49
  • The question of circular dependency is independent of design patterns, and is an entirely new question from the OP. Circular dependency is discussed in many other threads. At one time I collected a few of them in [Spot problems with circular dependency](https://stackoverflow.com/a/37445480/1371329). The short answer is dependency injection. DI containers have long ago solved this problem. – jaco0646 May 28 '21 at 13:49