Search code examples
c++c++11state-pattern

Avoid public `SetState()` interface in state pattern implementation in C++


The state pattern

Class diagram

itself is really nice pattern for implementing state machines because it allows to encapsulate state transitions logic in states themselves and adding a new state is actually becomes easier because you need to make changes only in relevant states.

But, it is usually avoided in description how should states be changed.

If you implement state change logic in Context then whole the point of pattern is missed, but if you implement state change logic in states, that means you need to set a new state in Context.

The most common way is to add the public method to Context SetState() and pass reference to Context to the state object, so it will be able to set a new state, but essentially it will allow the user to change state outside the state machine.

To avoid it I came to the following solutions:

class IContext {
     public:
         virtual void SetState(unique_ptr<IState> newState) = 0;
}

class Context : public IContext {
     private:
         virtual void SetState(unique_ptr<IState> newState) override { ... };
}

But in general changing the method scope in derived class doesn't look really good.

Is there another way to hide this interface (friend class is not an option because it requires to change the Context class for each state being added)?


Solution

  • You could consider having the handler handle()returning the next state...

    class IState {
         public:
             virtual unique_ptr<IState> handle(Context&) = 0;
    };
    
    class StateA : public IState {
         private:
             // presented inline for simplicity, but should be in .cpp
             // because of circular dependency.
             //
             virtual unique_ptr<IState> handle(Context& ctx) override
             {
                 //...
                 if (/*...*/)
                   return make_unique(StateB{});
    
                 //...  including other state switch..
    
                 return { nullptr };  // returning null indicates no state change, 
                                      // returning unique_ptr<>(this) is not really an option.
             }
    };