Design patterns 处理多个状态的输入和状态更改的最佳方法?
我有一个具有多个状态的应用程序,每个状态对输入的响应不同 最初的实现是用一个大的switch语句完成的,我使用state模式对其进行了重构(至少,我认为这是state模式。我对使用设计模式有点陌生,所以我倾向于混淆它们)-Design patterns 处理多个状态的输入和状态更改的最佳方法?,design-patterns,refactoring,Design Patterns,Refactoring,我有一个具有多个状态的应用程序,每个状态对输入的响应不同 最初的实现是用一个大的switch语句完成的,我使用state模式对其进行了重构(至少,我认为这是state模式。我对使用设计模式有点陌生,所以我倾向于混淆它们)- class应用程序{ 公众: 静态应用程序*getInstance(); void addState(int state_id,AppState*state){u states[state_id]=state;} void setCurrentState(int state_
class应用程序{
公众:
静态应用程序*getInstance();
void addState(int state_id,AppState*state){u states[state_id]=state;}
void setCurrentState(int state_id){{u current_state=_states[state_id];}
私人:
App()
~App();
标准::地图(美国);
AppState*\u当前\u状态;
静态应用程序*\u实例;
}
类AppState{
公众:
虚拟void handleInput()=0;
虚拟~AppState();
受保护的:
AppState();
}
目前,每个州都在轮询操作系统的输入,并相应地采取行动。这意味着每个具体的状态都有一个巨大的switch语句,每个有效的按键都有一个case。有些情况下调用函数,而另一些情况下使用App::setCurrentState(newstate)发出状态更改。问题在于,在一个状态下执行某项操作的密钥在另一个状态下可能不会执行任何操作(或者在极少数情况下,可能会执行不同的操作)
好吧,我想这就是相关的背景。以下是实际问题-
首先,消除具体状态中的巨大switch语句的最佳方法是什么?建议使用命令模式,但我不明白在这里如何使用它。有人能帮我解释一下,或者提出另一个解决方案吗
作为旁注,我考虑过(也不反对)让App类对操作系统进行轮询,然后将输入传递到_current_state->handleInput。事实上,有些事情告诉我,我想把这作为重构的一部分。我只是还没做完
其次,通过调用App::setCurrentState(newstate)进行状态更改。我意识到这类似于使用globals,但我不确定是否有更好的方法。我的主要目标是能够在不修改App类的情况下添加状态。这里也欢迎您的建议。我写了一个库,在其中我重构了很多州的东西。它是干净的、非常面向对象的,并且不会增加很多开销,但是它是一种非常不同的编写状态机代码的方法 它包括一些测试,如果没有其他的,他们可能会给你一些想法
如果愿意,欢迎您查看它:如果您能够捕获类中的所有操作系统输入,那么您可以拥有一个侦听输入的对象,并使用责任链模式通知操作系统输入的特定操作。您可以这样看: 我有一个状态(你的应用程序)的静态容器,还有很多状态(你的应用程序状态)可以包含数据,并且只有一个处理程序 相反,请将其视为: 我有一个单状态机类。(我可能有很多这样的例子,也可能没有。)这包含了与外部世界互动所需的数据。还包含一组静态EventHandler,每个状态一个。这些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
我已经重构了一些东西- 通过要求将指向状态机(App)的指针传递给AppState构造函数,我消除了对App::setCurrentState的直接调用。这样,所有必要的调用都可以通过该指针进行 我已经在handleInput中添加了一个参数,并使其能够让应用程序进行操作系统输入轮询,并将任何输入传递到当前状态 新代码如下所示-
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;
}
class应用程序{
公众:
静态应用程序*getInstance();
void addState(int state_id,AppState*state){u states[state_id]=state;}
void setCurrentState(int state_id){{u current_state=_states[state_id];}
私人:
App()
~App();
标准::地图(美国);
AppState*\u当前\u状态;
静态应用程序*\u实例;
}
类AppState{
公众:
虚拟void handleInput(int keycode)=0;
虚拟~AppState();
受保护的:
AppState(App*App);
AppState*\u应用程序;
}
因此,在每一个状态中,仍然会留下一个大型switch语句,将按键转换为特定于状态的操作。我想我可以用一个键到动作的映射来替换这个开关,但我仍然想知道是否有更好的方法。考虑到您的重构,现在的问题似乎是如何减少在各种具体AppState实现中重复的键代码解析代码的数量。正如您所提到的,这将导致多个switch语句,这些语句选择要调用哪个代码来处理击键输入
根据此代码对性能的关键程度,您可以将该键码解码逻辑分离到App中的processInput(int keycode)方法中(或作为AppState中的具体方法),并在AppState类中创建一组handle*Pressed()函数。根据您正在处理的击键类型的不同,这可能是合理的,或者可能会导致实现太多的方法。将输入封装到一个类中就足够简单了。尽管如此,我不明白责任链是如何运作的,因为一次只有一个州处于活动状态。谢谢你们的投入。您的确切方法对我来说不起作用(例如,我不能定义编译时要使用的状态),但我已经验证了我正在朝着正确的方向前进。然而,如果特定于状态的数据不应该存储在状态中,那么它应该存储在哪里?您不希望类State1从类state派生吗?
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;
}