Лучший способ обрабатывать ввод и изменения состояния для нескольких состояний? - PullRequest
1 голос
/ 06 декабря 2008

У меня есть приложение с несколькими состояниями, каждое из которых по-разному реагирует на ввод.

Первоначальная реализация была сделана с помощью большого оператора 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 в конкретных состояниях? Этот вопрос предлагает образец команды, но я не понимаю, как бы я использовал его здесь. Может кто-нибудь помочь объяснить это или предложить другое решение?

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

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

Ответы [ 5 ]

1 голос
/ 31 декабря 2008

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

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

1 голос
/ 06 декабря 2008

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

Я исключил прямые вызовы 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;

}

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

0 голосов
/ 06 декабря 2008

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

0 голосов
/ 06 декабря 2008

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

У меня есть статический контейнер для состояний (ваше приложение) и множество состояний (вы - 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 голосов
/ 06 декабря 2008

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

Включает в себя пару тестов, если они не дадут вам никаких идей.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...