1

I'm implementing a state pattern in C++ with a context and several states. Each state implements its transition. Here's a simplified version of what that design could look like:

class Context;
class State
{
  public:
    State(Context* ctx);
    virtual void transition() = 0; 
  protected:
    Context* context; 
}

class Context{
  public:
   void transition();
   void setState(State* next); 
  private:
   State* current; 
}

class StateA : public State
{
  public:
    StateA(Context* ctx);
    void transition() override;
}

class StateB : public State
{
  public:
    StateB(Context* ctx);
    void transition() override;
}

Context::setState(State* newState)
{
  current = newState;
}

Context::transition()
{
  current->transition();
}

StateA::transition()
{
  context->setState(new StateB(context));
}

StateB::transition()
{
  // noop
}

Now, I need to add a new state transition that requires an integer parameter, like this:

StateB::transition(int a) // This doesn't match the base class!
{
  if (a > 5) 
  { 
    context->setState(new StateA(context)); 
  } 
}

I want to avoid rewriting the entire state pattern for this new requirement since 90% of the code remains the same. How can I modify my current implementation to accommodate state transitions with additional parameters while reusing the existing code?

New contributor
Emile Papillon-Corbeil is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
2
  • Where does that integer come from? States should have.. well, state. Maybe redesign StateB to have ownership of that mysterious a.
    – sfiss
    Commented Jul 11 at 6:46
  • The integer is provided at the application level. Application owns the context, the context owns states. The application runs in cycles and transitions the context on each cycle. It has a few parameters that are used to transition 2 states (of a total of 4) states. I am also not mentioning that my actual use case has two concrete "contexts", one of which does not handle states with these additional parameters - so I want to avoid having setters for the parameters at the context level. Commented 2 days ago

1 Answer 1

1

The extra parameter will only make sense in a layer of the code where the class StateB "lives". Let me call this "application layer".

Your state pattern implementation, especially the classes Context and State, however, lives inside some "framework layer", which does not know anything about the specific StateB and it's requirements for an extra parameter.

The straightforward solution here is to make the parameter a a member of the class StateB, which has to be initialized before Context::transition() is called. The initialization has to happen somewhere inside the application layer. You have basically the following options for this:

  • Pass the initial parameter value in the StateB constructor - that requires each constructor call to look like new StateB(context,initialA). That's feasible when initialA is available at all places and at the time where new StateB is called. In your example, this happens inside the class StateA, and it is not clear if initialA is accessable in that class, or a the point in time when StateB is constructed.

  • Provide an extra setter method StateB::setA(int initialA) and call it somewhere in the application layer where initialA is known, but, before Context::transition. That may require detection of when Context::current is of type StateB and a downcast. It also has some risk to be forgotten to be called and it gives StateB some mutable state.

If none of these options works for you, you can also try to extend the framework in general:

  • Create an abstraction for "potentially extra parameters" in the framework layer, for example, an ExtraParameters class, and extend the signature of State::transition and Context::transition by this ExtraParameters. All states which don't need this parameter for a transition should ignore it, but StateB::transition can extract the parameter a from it.

    This will have the biggest impact on your current framework implementation, but it could be sensible when different State subclasses require different forms of extra parameters. There are some different possible design alternatives for ExtraParameters, you have to think which one makes most sense for your case.

What to choose depends on your real-world context, how many State types require what kind of parameters, and where and when the parameters are become available in the system, and where or how often they change - that is something only you know, since you left no clue for us about these details in the question.

2
  • Having a setter in StateB works. My problem is finding an elegant way to keep a handle on StateB at my application level, but this is a different problem so I will mark it as resolved. Commented 2 days ago
  • Attempt #1 godbolt.org/z/TecP3645b Note: A lot of complexity comes from the fact that I cannot store pointers that will bind to other values after construction (for reasons related to the middleware) - so I had to provide an enum value to determine the current state with a way to convert it to state objects. Commented 2 days ago

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.