Покритикуйте реализацию паттерна State
От: syomin  
Дата: 24.12.07 08:23
Оценка:
Добрый день!

Столкнулся со сложностями в реализации паттерна State. GoF читал, но просветления не пришло, поэтому решил обратиться к местным гуру за помощью.

Есть класс Device, поведение которого сильно зависит от его состояния. Интерфейс у класса следующий:
class Device {
public:
        void poll();
        void process_command(const Command *command);
};


Само-собой напрашивается паттерн State. Взвесив все за и против, решил хранить данные, связанные с конкретным состоянием, в классе Device, а класс State сделать синглтоном. Задача усложняется тем, что при попадании в некоторое состояние и при выходе из него нужно выполнять определенные действия, поэтому в State добавляются методы entry() и exit().
class Device {
public:
        void poll() { state_->poll(*this); }
        void process_command(const Command *command) { state_->process_command(*this, command); }

private:
        class State {
                virtual void entry(Device &device);
                virtual void exit(Device &device);

                virtual void poll(Device &device);
                virtual void process_command(Device &device, const Command *command);
        };

        // Состояния устройства.
        class Ready;
        class Steady;
        class Go;

        const State *state_;

        // Сменить состояние.
        void change_state(State *state)
        {
                state_->exit(*this);
                state_ = state;
                state_->entry(*this);
        }
};


Внимание, вопрос: какие минусы в такой "конструкции"?

Лично мне не нравится, что для кода, выполняющего при входе (выходе) из состояния, используются отдельные методы, а не пара конструктор-деструктор.
Re: Покритикуйте реализацию паттерна State
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 24.12.07 08:45
Оценка:
Здравствуйте, syomin, Вы писали:

S>Добрый день!


S>Столкнулся со сложностями в реализации паттерна State. GoF читал, но просветления не пришло, поэтому решил обратиться к местным гуру за помощью.


S>Есть класс Device, поведение которого сильно зависит от его состояния. Интерфейс у класса следующий:

S>...

А какие события вызывают переключение состояния. Может лучше создать несколько методов SetState_statename_() и использовать new и delete для объектов состояний, тогда можно логику enter и exit вынести в конструктор и деструктор.
Re[2]: Покритикуйте реализацию паттерна State
От: syomin  
Дата: 24.12.07 08:59
Оценка:
G>А какие события вызывают переключение состояния. Может лучше создать несколько методов SetState_statename_() и использовать new и delete для объектов состояний, тогда можно логику enter и exit вынести в конструктор и деструктор.

Забыл уточнить. Переходы между состояниями происходят в следующих случаях:
Re[3]: Покритикуйте реализацию паттерна State
От: dream_cast Россия  
Дата: 24.12.07 14:30
Оценка:
ИМХО, нормальная реализация.
У самого как-то давно уже была подобная проблема и решил ее именно так, как и ты.

А то, что entry/exit реализуются явными вызовами извне, а не в ctro/dtor, так в этом есть свои плюсы.
Например, нет необходимости убивать объекты-состояния при выходе из этого состояния и создавать новый объект-состояние при входе в него.
В некоторых случаях это может быть полезно с т.з. производительности.
Плюс это может быть более надежным в плане обработки исключительных ситуаций (вдруг новый объект-состояние не сможет быть создан динамически при смене состояния (E_OUTOFMEMORY)).

Ну и еще такой момент, как возврат в состояние. Иногда бывает необходимо, чтобы при возврате в определенное состояние это самое состояние не начинало свою работу "с нуля", а продолжало с того места, где его прервали. Т.е. само состояние должно обладать памятью:
class CWorker
{
  Dig();
  Build();
  Think();

  class CConcreteWorker
  {
     virtual DoWork() = 0;
  };
  class CDigger : CConcreteWorker;
  class CBuilder : CConcreteWorker;
  class CThinker : CConcreteWorker;


  DoWork()
  {
    m_worker->DoWork();
  }

  CConcreteWorker *m_worker;
}

main()
{
  CWokrer w;
  w->Dig();
  // some code
  w->Think();
  // some code
  w->Dig(); // Копать заново или с того места где остановились в прошлый раз? :)
}
Re: Покритикуйте реализацию паттерна State
От: stenkil  
Дата: 24.12.07 15:09
Оценка:
Здравствуйте, syomin, Вы писали:


Более логично чтобы каждый State сам определял свое следующее состояние. Тогда логика перехода будет реализована в пределах конкретного StateConcrete, соответствующуя этому StateConcrete и не будет засорять общий код. Почти то же с небольшими изменениями:
class State{
  private:
    Device *device_;
  protected:
    virtual void entry(Device *device);
    virtual void exit(Device *device);
  public:

    State(Device *device){
      ............
      entry(device_);}

    ~State(){
      exit(device_);
      ...........}

    virtual State *pool(Device *device);
    virtual State *process_command(Device *device, const Command *commad);
}

class Device {
private:
  State *state_;
public:
        void poll() { state_ = state_->poll(*this); }
        void process_command(state_ = const Command *command) { state_->process_command(*this, command); }
}
Re: Покритикуйте реализацию паттерна State
От: Maxim S. Shatskih Россия  
Дата: 25.12.07 15:08
Оценка:
S>Само-собой напрашивается паттерн State. Взвесив все за и против, решил хранить данные, связанные с конкретным состоянием, в классе Device, а класс State сделать синглтоном.

Что значит синглтоном? а если девайсов будет два? синглтон тут не применим.

Получается два класса Device и Statе, объекты которых всегда в соотношении 1 к 1. Кандидаты на рефакторинг путем объединения зачем их два делать-то? незачем.

S>Задача усложняется тем, что при попадании в некоторое состояние и при выходе из него нужно выполнять определенные действия, поэтому в State добавляются методы entry() и exit


Все не так. У стейт-машины действия связаны с ребром графа, соединяющем два состояния, там нет понятия "вход" и "выход" из состояния. При этом ничто не мешает связать те же два состояния несколькими разными ребрами, какое из них исполнится — зависит не от состояния, а от типа внешнего воздействия.

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

Каждый из этих методов есть такой большой switch по текущему состоянию (из которого выходим). Совокупность (ТипВоздействия, ТекущееСостояние) однозначно задает ребро графа, которое будет исполняться, а значит — и действия, и новое состояние. Все это просто прописывается в ветке свитча, если надо — то выносится в отдельную функцию.

В общем, самая лучшая архитектура — это хорошо продуманное старое, еще на Си реализуемое.
Занимайтесь LoveCraftом, а не WarCraftом!
Re[2]: Покритикуйте реализацию паттерна State
От: Maxim S. Shatskih Россия  
Дата: 25.12.07 15:17
Оценка:
G>А какие события вызывают переключение состояния. Может лучше создать несколько методов SetState_statename_()

Нет, не лучше. Дело в том, что у стейт-машины выполняемые действия связаны с ребром, а не с финальным состоянием. Если в состояние ведут 2 разных ребра — то связанные с ними действия могут быть абсолютно разными, а потому метод SetState бессмысленен, и заменяется одним присваиванием в m_State.

Что действительно может иметь смысл, так это полиморфизм по текущему (а не таржет-) состоянию. Дело в том, что исполняемое ребро однозначно выбирается по комбинации (ТекущееСостояние, ВидВнешнегоВоздействия). Таким образом, если мы делаем свою таблицу указателей на функции для каждого текущего состояния, функции в которой отличаются в зависимости от типа внешних воздействий — то задача выбора ребра исполняется не свитчом, а вшитым в язык полиморфизмом. Собственно, значение m_OpTable может заодно использоваться просто для хранения текущего состояния, и переход в новое состояние будет чем-то типа m_OpTable = &GlobalOpTableForState1;

На Си делается элементарно, на классических ОО языках — чуть сложнее там для реализации этих таблиц функций придется, скорее всего, использовать синглетоны без данных (только код), и передавать в эти синглетонные функции свой объект state machine как параметр.

G>и использовать new и delete для объектов состояний


Просто прелесть какая-то! теперь не просто объект State, а объект, инстанс которого предполагается разрушать и создавать новый при каждом прохождении ребра. Во бред-то! чем enum State хуже? тем, что он избавляет от избыточных new и delete?
Занимайтесь LoveCraftом, а не WarCraftом!
Re[4]: Покритикуйте реализацию паттерна State
От: Maxim S. Shatskih Россия  
Дата: 25.12.07 15:21
Оценка:
_>Ну и еще такой момент, как возврат в состояние. Иногда бывает необходимо, чтобы при возврате в определенное состояние это самое состояние не начинало свою работу "с нуля", а продолжало с того места, где его прервали. Т.е. само состояние должно обладать памятью:

Количество "мест возврата" конечно. Тогда все они объявляются состояниями, стейт-машина растет. Это гибко и разумно (никакой "памяти"), хотя кодировать туповато.

Совсем другое дело, если нужна многоуровневая память — помнить то, что было до, что было "до до", и так далее. Тогда вообще стейт-машина неприменима, а нужна стековая машина.
Занимайтесь LoveCraftом, а не WarCraftом!
Re[5]: Покритикуйте реализацию паттерна State
От: dream_cast Россия  
Дата: 25.12.07 15:53
Оценка:
Здравствуйте, Maxim S. Shatskih, Вы писали:

MSS>Количество "мест возврата" конечно. Тогда все они объявляются состояниями, стейт-машина растет. Это гибко и разумно (никакой "памяти"), хотя кодировать туповато.


Не совсем понял что имеется ввиду.

Если вернуться к моему примеру выше, то CDigger, CBuilder & CThinker это и есть конретные состояния, одно из которых является активным в какой-то момент времени.

А под "обладать памятью" я имел ввиду, что каждое конкретное состояние может, ну скажем вести статистику своей работы и сохранять ее в промежутках между переходами из одного состояния в другое:
class CDigger : CConcreteWorker
{
  entry()
  {
     m_diggedHoles = 0; // Или не делать этого в случае с "продолжением"
  }
  exit()
  {
    LogMessage("%d holes digged", m_diggedHoles);
  }

  DoWork()
  {
     m_diggedHoles++;
  }

private:
  int m_diggedHoles;
}


Можно пример "на пальцах"?
Re[2]: Покритикуйте реализацию паттерна State
От: syomin  
Дата: 26.12.07 07:14
Оценка:
Добрый день!

MSS>Что значит синглтоном? а если девайсов будет два? синглтон тут не применим.

Может, я неправильно выразился, классы, соответствующие конкретным состояниям сделаны синглтонами, т.к. никаких данных в них не храниться. В общем, ничего нового — в точности по книжке GoF.
Re[2]: Покритикуйте реализацию паттерна State
От: stenkil  
Дата: 26.12.07 08:09
Оценка:
Здравствуйте, Maxim S. Shatskih, Вы писали:

S>>Само-собой напрашивается паттерн State. Взвесив все за и против, решил хранить данные, связанные с конкретным состоянием, в классе Device, а класс State сделать синглтоном.


MSS>Что значит синглтоном? а если девайсов будет два? синглтон тут не применим.


Ну и что, пусть будет 10 device, если функции используют локальные переменные, какие проблемы от того что девайсы будут держать указатель на один и тот же стейт ?
Re[3]: Покритикуйте реализацию паттерна State
От: Maxim S. Shatskih Россия  
Дата: 26.12.07 13:37
Оценка:
S>Ну и что, пусть будет 10 device, если функции используют локальные переменные, какие проблемы от того что девайсы будут держать указатель на один и тот же стейт ?

То, что девайсы могут находиться в разных состояниях — не смущает?
Занимайтесь LoveCraftом, а не WarCraftом!
Re[4]: Покритикуйте реализацию паттерна State
От: stenkil  
Дата: 26.12.07 14:29
Оценка:
Здравствуйте, Maxim S. Shatskih, Вы писали:

MSS>То, что девайсы могут находиться в разных состояниях — не смущает?

Ну и будет держать каждый указатель на свой state.
Прочитал еще раз ветку, ссори, говорим об одном и том же, вы уже писали здесь
Автор: Maxim S. Shatskih
Дата: 25.12.07

>> использовать синглетоны без данных (только код)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.