2008-12-05 4 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(); 

} 

В настоящее время каждое государство производит опрос ОС для ввода и действует соответственно. Это означает, что каждое конкретное состояние имеет огромный оператор switch с аргументом для каждого допустимого нажатия клавиши. Некоторые случаи вызывают функции, а другие случаи вызывают изменения состояния с помощью App :: setCurrentState (newstate). Уловка состоит в том, что ключ, который делает что-то в одном состоянии, может ничего не делать (или в редких случаях, может делать что-то другое) в другом состоянии.

Хорошо, я думаю, что это уместный фон. Вот фактический вопрос (вопросы) -

Во-первых, что является лучшим способом устранения огромных операторов switch в конкретных состояниях? This question предлагает шаблон команды, но я не понимаю, как я буду использовать его здесь. Может кто-нибудь помочь объяснить это или предложить другое решение?

В качестве побочного примечания я рассмотрел (и не против) идею позволить классу App выполнить опрос ОС, а затем передать входы в _current_state-> handleInput. На самом деле, что-то говорит мне, что я хочу сделать это как часть рефакторинга. Я еще не сделал этого.

Во-вторых, изменения состояния производятся путем вызова App :: setCurrentState (newstate). Я понимаю, что это сродни использованию глобалов, но я не уверен в лучшем способе сделать это. Моя главная цель - добавить состояния без изменения класса App. Предложения также приветствуются здесь.

ответ

1

Учитывая ваш рефакторинг, похоже, теперь вопрос заключается в том, как уменьшить количество кода разбора кода ключа, который будет дублироваться в различных конкретных реализациях AppState. Как вы заметили, это приводит к появлению нескольких операторов switch, которые выбирают, какой код вызывать для обработки ввода нажатия клавиши.

В зависимости от того, насколько эффективен этот код, вы можете отделить эту логику декодирования кода в методе processInput (int keycode) в приложении (или как конкретный метод в AppState) и создать набор дескрипторов * нажатых() функций в классах AppState. В зависимости от того, сколько типов нажатий клавиш вы просматриваете, это может быть разумным, или это может привести к слишком большому количеству методов для реализации.

0

Я написал библиотеку, где я реорганизовал много материала штата. Это чистое и довольно OO, и на самом деле не добавляет много накладных расходов, но это совсем другой способ кодирования конечного автомата.

Он включает в себя пару тестов, если ничего другого они не могут дать вам некоторые идеи.

Вы всегда можете посмотреть на него, если вам нравится: http://code.google.com/p/state-machine/

0

Если вы в состоянии захватить весь ввод ОС в классе, то вы можете иметь один объект, который прослушивает для ввода и использовать цепочку шаблон ответственности для уведомления о конкретных действиях ввода ОС.

+0

Инкапсулирование ввода в один класс будет достаточно простым. Хотя, Я не понимаю, как будет действовать цепочка ответственности, поскольку одновременно будет действовать только одно состояние. – 2008-12-05 23:34:42

0

Вы смотрите на вещи, как:

У меня есть статический контейнер для состояний (ваш App), и много состояний (вы 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 
+0

Спасибо за ваш вклад. Ваш точный подход не будет работать для меня (например, я не могу определить состояния, которые будут использоваться во время компиляции), но для меня проверено, что я двигаюсь в правильном направлении. Однако, если данные состояния не должны храниться в состоянии, где его следует хранить? – 2008-12-06 05:32:43

1

Я переработан вещи немного -

Я устранил прямые вызовы App :: setCurrentState, требуя, чтобы указатель на государственной машины (App) передается в конструктор AppState. Таким образом, все необходимые вызовы могут быть сделаны с помощью этого указателя.

Я добавил параметр 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; 

} 

Так что это все еще оставляет в каждом государстве, большое заявлении переключателя, который преобразует нажатие клавиш бюджетников конкретных действий. Полагаю, я мог бы заменить коммутатор картой ключей на действия, но мне все еще интересно, есть ли лучший способ.