snippetcppMinor
How do I avoid explicit type switching when operation based on state?
Viewed 0 times
switchingexplicitavoidtypeoperationstatebasedhowwhen
Problem
I've encountered a problem that I have to solve using
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
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
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());
// }
}
};
#endifParserStates.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
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):
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.
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.