HiveBrain v1.2.0
Get Started
← Back to all entries
snippetcppMinor

How do I avoid explicit type switching when operation based on state?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
switchingexplicitavoidtypeoperationstatebasedhowwhen

Problem

I've encountered a problem that I have to solve using dynamic_cast to invoke different functions, depending on the type of class State family.

I was re-factoring a Parser I wrote before, from a freaking nested class State inside Parser so that I could separate them and extract common parts using templates.

Here is a minimal model to illustrate the problem. It is not the code from the project, so I do not separate out the implementation here.

Parser.h

Operation() to dispatch OperationStateXX() based on type of current state mState

#ifndef PARSER_H
#define PARSER_H

#include "ParserStates.hxx"

class Arg;
class Parser
{
public:
private:
    const ParserBaseState* mState;

    void
    Transit(const ParserBaseState &state)
    { mState = &state; }

    void
    Operation(const Arg &arg)
    {
        if (dynamic_cast* >(mState))
        {
            OperationState1(arg);
        }
    }

    void
    OperationState1(const Arg &arg)
    {
//        if (someCase)
//        {
//            Transit(ParserState::GetInstance());
//        }
    }
};

#endif


ParserStates.hxx

Defines several State the Parser may have, each State uses Singleton Pattern since I think specific State have multiple instance does not make sense.

I know Singleton pattern is controversial, but it seems fit what I think this model
should be here. However, I met "static virtual" problem in C++ while fix the model in the middle.

```
#ifndef PARSER_STATES
#define PARSER_STATES

struct ParserStates
{
struct State1 { typedef bool ParserStateTrait; };
struct State2 { typedef bool ParserStateTrait; };
struct State3 { typedef bool ParserStateTrait; };
};

class ParserBaseState
{
protected:
virtual ~ParserBaseState() {}
};

template
class ParserState
: public ParserBaseState
{
public:
static ParserState &
GetInstance()
{
static ParserState instance;
return instance;
}
private:
typedef typ

Solution

Maybe it's a result of your simplification for the example but your ParserState looks like an over-engineered enum to me. What do you gain from it over using an enum and a switch for the current state?

In general whenever you need to check for the specific type in order to execute some specific logic then your abstraction is probably flawed from an OO point of view.

One design I've chosen in the past for more complicated parser is to have the state execute the transition. Something along these lines (does not compile just showing the idea):

class IState
{
    public:
         virtual IState* Transition(std::string& token) = 0;
         virtual bool IsFinalState() = 0;
}

class InitState : IState
{
   ...
}

class SomeState : IState
{
   ...
}

class FinalState : IState
{
   ...
}

class Tokenizer
{
    public:
          Tokenizer(const std::string& input) { ... }
          std::string NextToken() { ... } 
          bool HasToken() { ... }
}

class Parser
{
    public:

          void Parse(const std::string& input)
          {
              Tokenizer tokenizer(input);

              IState* currentState = new InitState();

              while (!currentState->IsFinalState() && tokenizer.HasToken())
              {
                  IState* newState = currentState->Transition(tokenizer.NextToken());
                  delete currentState;
                  currentState = newState;
              }
          }
}


So the states themselves execute the code required for the transition into the next state given the current token. You can also pass around a context object if required.

Code Snippets

class IState
{
    public:
         virtual IState* Transition(std::string& token) = 0;
         virtual bool IsFinalState() = 0;
}

class InitState : IState
{
   ...
}

class SomeState : IState
{
   ...
}

class FinalState : IState
{
   ...
}

class Tokenizer
{
    public:
          Tokenizer(const std::string& input) { ... }
          std::string NextToken() { ... } 
          bool HasToken() { ... }
}

class Parser
{
    public:

          void Parse(const std::string& input)
          {
              Tokenizer tokenizer(input);

              IState* currentState = new InitState();

              while (!currentState->IsFinalState() && tokenizer.HasToken())
              {
                  IState* newState = currentState->Transition(tokenizer.NextToken());
                  delete currentState;
                  currentState = newState;
              }
          }
}

Context

StackExchange Code Review Q#38114, answer score: 2

Revisions (0)

No revisions yet.