我有一个具有多个状态的应用程序,每个状态对输入的响应不同。
初始实现是使用一个大的switch语句完成的,我使用状态模式重构(至少,我认为它是状态模式。我对使用设计模式有点新意,所以我倾向于让它们混淆) -
class App {
public:
static App * getInstance();
void addState(int state_id, AppState * state) { _states[state_id] = state; }
void setCurrentState(int state_id) { _current_state = _states[state_id]; }
private:
App()
~App();
std::map<int, AppState *> _states;
AppState * _current_state;
static App * _instance;
}
class AppState {
public:
virtual void handleInput() = 0;
virtual ~AppState();
protected:
AppState();
}
目前,每个州都在轮询操作系统以进行输入,并采取相应的行动。这意味着每个具体状态都有一个巨大的switch语句,每个有效按键都有一个case。某些情况下调用函数,其他情况通过使用App :: setCurrentState(newstate)发出状态更改。问题在于,在一个州内做某事的钥匙可能在另一个州没有做任何事情(或在极少数情况下,可能做些不同的事情)。
好的,我认为这是相关的背景。这是实际问题 -
首先,消除具体状态中巨大的switch语句的最佳方法是什么? This question建议命令模式,但我不明白我将如何在这里使用它。有人可以帮忙解释一下,或建议另一种解决方案吗?
作为旁注,我考虑过(并且不反对)让App类进行操作系统轮询,然后将输入传递给_current_state-&gt; handleInput的想法。事实上,有些东西告诉我,我想在重构过程中做到这一点。我还没有完成它。
其次,通过调用App :: setCurrentState(newstate)进行状态更改。我意识到这类似于使用全局变量,但我不确定是否有更好的方法。我的主要目标是能够在不修改App类的情况下添加状态。这里也欢迎提出建议。
答案 0 :(得分:1)
我已经重构了一些东西 -
我已经通过要求将状态机(App)的指针传递给AppState构造函数来消除对App :: setCurrentState的直接调用。这样,所有必要的调用都可以通过该指针进行。
我已经为handleInput添加了一个参数,并使得App执行操作系统输入轮询并将任何输入传递给当前状态。
新代码如下所示 -
class App {
public:
static App * getInstance();
void addState(int state_id, AppState * state) { _states[state_id] = state; }
void setCurrentState(int state_id) { _current_state = _states[state_id]; }
private:
App()
~App();
std::map<int, AppState *> _states;
AppState * _current_state;
static App * _instance;
}
class AppState {
public:
virtual void handleInput(int keycode) = 0;
virtual ~AppState();
protected:
AppState(App * app);
AppState * _app;
}
所以这仍然留在每个状态,一个大的switch语句将keypresses转换为特定于状态的动作。我想我可以用行动键来替换开关,但我仍然想知道是否有更好的方法。
答案 1 :(得分:1)
鉴于您的重构,现在的问题似乎是如何减少将在各种具体AppState实现中重复的密钥代码解析代码的数量。正如您所提到的,这会导致多个switch语句选择调用哪些代码来处理击键输入。
根据此代码的性能关键程度,您可以将该键码解码逻辑分离为App中的processInput(int keycode)方法(或作为AppState中的具体方法)并创建一组句柄* Pressed()函数你的AppState课程。根据您正在处理的键击类型,这可能是合理的,或者可能导致实现的方法太多。
答案 2 :(得分:0)
我写了一个库,在那里我重构了很多状态。它很干净而且相当OO,并没有真正增加很多开销,但它是一种完全不同的编码状态机的方法。
它包括一些测试,如果没有别的,他们可能会给你一些想法。
如果您愿意,欢迎您查看:http://code.google.com/p/state-machine/
答案 3 :(得分:0)
如果您能够捕获类中的所有OS输入,那么您可以拥有一个侦听输入的对象,并使用责任链模式来通知操作系统输入的特定操作。
答案 4 :(得分:0)
你把事情视为:
我有一个静态容器用于状态(你的应用程序),以及许多状态(你的AppState),它们可以包含数据,只有一个处理程序。
而是将其视为:
我有一个StateMachine类。 (我可能会或可能没有很多实例。)这包含与外界交互所需的数据。还包含一组静态的eventHandlers,每个状态一个。这些eventHandler类不包含任何数据。
class StateMachine {
public:
void handleInput() { //there is now only one dispatcher
if( world.doingInput1() )
_current_state->handleInput1( *this );
else if( world.doingInput2() )
_current_state->handleInput2( *this, world.get_Input2Argument() );
//...
}
//the states, just a set of event handlers
static const State& state1;
static const State& state2;
//...
StateMachine( OutsideWorld& world )
:world( world )
{
setCurrentState( StateMachine::state1 );
}
void setCurrentState( const State& state ) { _current_state = &state; }
OutsidWorld& world;
private:
State* _current_state;
};
class State {
public:
//virtual ~State(); //no resources so no cleanup
virtual void handleInput1( StateMachine& sm ) const {};
virtual void handleInput2( StateMachine& sm, int myParam ) const {};
//...
};
class State1 {
public:
//define the ones that actually do stuff
virtual void handleInput1( StateMachine& sm ) const {
sm.world.DoSomething();
sm.setCurrentState( StateMachine::state27 );
}
virtual void handleInput27( StateMachine& sm, int myParam ) const {
sm.world.DoSomethingElse( myParam );
};
};
const State& StateMachine::state1 = *new State1();
//... more states