2008-12-05 68 views
1

我有一个具有多个状态的应用程序,每个状态以不同的方式响应输入。处理多个状态的输入和状态变化的最佳方法?

最初的实现是用一个大的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(); 

} 

当前,每个状态轮询操作系统的输入,并采取相应的行动。这意味着每个具体状态都有一个巨大的开关语句,每个有效的按键都有一个情况。有些情况下调用函数,其他情况下通过使用App :: setCurrentState(newstate)来发布状态更改。问题在于,在另一个州执行某项操作的密钥可能无法做任何事情(或在极少数情况下可能会做出不同的事情)。

好的,我认为这是相关的背景。下面是实际问题 -

首先,消除具体状态下的巨大开关语句的最佳方法是什么? This question建议命令模式,但我不明白我会在这里使用它。有人可以帮助解释它,或者提出其他解决方案吗?作为一个便笺,我已经考虑(并且不反对)让App类执行操作系统轮询,然后将输入传递给_current_state-> handleInput。事实上,有些东西告诉我,我将要做这个重构的一部分。我还没有做到。

其次,通过调用App :: setCurrentState(newstate)进行状态更改。我意识到这与使用全局变量类似,但我不确定是否有更好的方法来做到这一点。我的主要目标是能够在不修改App类的情况下添加状态。建议也欢迎在这里。

回答

1

鉴于您的重构,现在的问题看起来像是如何减少将在各种具体AppState实现中复制的键码解析代码量。正如你所提到的,这导致了多个开关语句,它们选择要调用哪个代码来处理按键输入。您可以将该键码解码逻辑分离为App中的processInput(int keycode)方法(或作为AppState中的具体方法),并创建一组句柄* Pressed()函数,具体取决于此代码对性能的影响程度。在您的AppState类中。取决于您正在处理的按键类型的数量,这可能是合理的,或者可能导致实施太多的方法。

0

我写了一个库,我重构了很多状态的东西。它很干净,很OO,并且不会增加很多开销,但它是编写状态机的完全不同的方式。

它包括一些测试,如果没有别的,他们可能会给你一些想法。

如果你喜欢,欢迎你来看待它:http://code.google.com/p/state-machine/

0

如果能够捕捉到一个类中的所有操作系统的输入,那么你可以有一个对象来监听输入,并使用链责任模式来通知操作系统输入的特定操作。

+0

将输入封装到一个类将很容易。虽然, 我不明白责任链将如何工作,因为一次只有一个国家会活跃。 – 2008-12-05 23:34:42

0

你看看事情:

我有状态(你的App)一个静态的容器,和很多国家的(您届时AppState),它可以包含数据,只有一个处理程序。

反而把它看作:

我有一个StateMachine类。 (我可能会或可能不会有很多实例)。这包含了与外部世界进行交互所需的数据。还包含一组静态事件处理程序,每个状态一个。这些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 
+0

感谢您的意见。你的确切方法对我来说不起作用(例如,我无法定义在编译时使用的状态),但对于我来说,我正在向正确的方向移动进行验证。 但是,如果状态特定的数据不应该存储在状态中,它应该存储在何处? – 2008-12-06 05:32:43

1

我重构事情有点 -

我已经要求一个指向状态机(应用程序)传递到届时AppState构造淘汰到App :: setCurrentState直接调用。这样,所有必要的调用都可以通过该指针进行。

我已经为handleInput添加了一个参数,并且使得应用程序执行OS输入轮询并将任何输入传递到当前状态。

新的代码看起来是这样的 -

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语句。我想我可以用钥匙图来替换开关,但我仍然想知道是否有更好的方法。