处理多个状态的输入和状态更改的最佳方法是什么?

时间:2008-12-05 22:04:36

标签: design-patterns refactoring

我有一个具有多个状态的应用程序,每个状态对输入的响应不同。

初始实现是使用一个大的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类的情况下添加状态。这里也欢迎提出建议。

5 个答案:

答案 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