Search code examples
c++oopdesign-patternsstate-pattern

Is it a good use of state pattern to maintain currently selected object?


A typical scenario of state pattern involves states which are mostly different like closed_connection_state or open_connection_state. In my case, all the states are essentially the same but the operation needs to apply to the currently selected object.

Typically something like this is done using an index variable which points to the currently selected object but is using state pattern a better implementation to do this like in the example below?

class Object
{
    std::string _name;
public:
    Object(std::string name) : _name(name)
    {

    }

    void Perform()
    {
        std::cout << _name << " Perform called\r\n";
    }
};

class CurrentObject
{
    Object* _a;
    Object* _b;
    Object* _current;

public:

    CurrentObject(Object* a, Object* b) : _a(a), _b(b)
    {
        _current = a;
    }

    void Perform()
    {
        _current->Perform();

        // Update _current logic goes here, lets just switch
        // the state whenever `Perform` is called.
        if (_current == _a)
            _current = _b;
        else
            _current = _a;
    };

};

int main()
{
    Object a("a"); // program can be in a state when `a` is the current object.
    Object b("b"); // or b can become the current object as result of an operation on current object

    CurrentObject current(&a, &b); // it assigns the defaults

    // assume Perform() does its thing but it also needs to change the current selected object.
    // In this example, we assumes the current selection object is always swapped.
    current.Perform(); // operates on `a`, the default
    current.Perform(); // operates on `b` due state changed in above line
    current.Perform(); // operates on `a` doe to state changed in above line again
}

Solution

  • That is definitely a reasonable thing to do, if your states multiply (as state machines tend to) this may become a bit difficult to maintain, but it's actually a very good OO style implementation of a state machine.

    You probably want your states (a & b) to extend a common abstract state so that when functionality is the same across all states you don't have to implement it in every single object.

    For expansion you probably also want to "name" your states and put them in a hashtable, once it scales (remember, in programming you have 1 or many) adding a new state won't take a code change to your state machine--but I assume you already have something like this and just scaled things down for the question.

    Also note that to switch states you don't want to do it directly (as your example), you probably want a method (setState) that changes the state when the perform method returns, not in the perform method itself or while it's running. In fact, you can have perform return a string indicating it's next desired state..

    Edit from comments:

    What I meant by naming your states is instead of:

    class CurrentObject
    {
        Object* _a;
        Object* _b;
        Object* _current;
        ...
    

    You might have something like (Excuse my java-syntax, C# is not my primary language but I know it's very similar in functionality)

    class CurrentObject
    {
        Hashtable states=new Hashtable();
        Object* _current;
    
        public addState(String stateName, Object* state)
        {
            states.put(stateName, state)
        }
    
        public void Perform()
        {
            String nextState = _current->Perform();
            if(nextState != null)
                setState(nextState);
        }
        public void setState(String stateName)
        {
            _current = states.get(stateName);
        }
    
    }
    

    Your calling code would do something like:

    currentObject = new CurrentObject()
    currentObject.addState("state a", _a);
    currentObject.addState("state b", _b); 
    currentObject.setState("state a");
    currentObject.perform();
    ...
    

    I'm ignoring a LOT of initialization and error checking.

    Your state machine currently only has one event right now: "Perform()". You may find you need other events which will complicate things a little (In java I'd possibly use reflection or annotations to solve THAT problem, not sure how C# would do it).