Представление государственной машины


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

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

    • OnEvent(...);
    • OnEnterState(...);
    • OnExitState(...);

    Из StateMachine::OnEvent(...) я переправляю событие в CurrentState::OnEvent(...) и здесь принимается решение сделать переход или нет. По переходу звоню CurrentState::OnExitState(...), NewState::OnEnterState() и CurrentState = NewState;

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

    • InitialState
    • FinalState
    • OnEvent(...)
    • DoTransition(...)

    Из StateMachine::OnEvent(...) я передаю событие всем переходам, где InitialState имеет то же значение, что и CurrentState в государственной машине. Если условие перехода выполнено, цикл останавливается, вызывается метод DoTransition и CurrentState устанавливается в Transition::FinalState.

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

Какой подход, по вашему мнению, лучше подходит для моделирования графического интерфейса. Знаете ли вы другие представления, которые могут быть лучше для моей проблемы?

5 3

5 ответов:

Вот третий вариант:

  • представить государственную машину в виде матрицы переходов
    • индекс столбца матрицы представляет состояние
    • индекс строки матрицы представляет собой symbol (см. ниже)
    • Ячейка матрицы представляет состояние, в которое должен перейти мачихе. Это может быть как новое состояние, так и одно и то же состояние
  • каждое состояние имеет метод OnEvent, который возвращает symbol

Из StateMachine::OnEvent(...) события передаются в State::OnEvent, который возвращает a symbol - a результат исполнения. StateMachine затем на основе текущего состояния и возвращенного символа решает, является ли

  • переход в другое состояние должен быть сделан, или
  • текущее состояние сохраняется
  • необязательно, если переход совершен, OnExitState и OnEnterState вызывается для соответствующих состояний

Пример матрицы для 3 состояний и 3 символов

0 1 2
1 2 0
2 0 1

В этом примере, если машина находится в любом из состояний (0,1,2) и State::OnEvent возвращает символ 0 (первая строка в матрице) - он остается в том же состоянии

Вторая строка говорит, что если текущее состояние 0 и возвращаемый символ 1, то переход осуществляется в состояние 1. Для состояния 1 - > состояние 2 и для состояния 2 - > состояние 0.

Аналогично третьей строке написано, что за символ 2, гос. 0-> государство 2, гос. 1 -> государство 0, гос. 2 -> государство 1

Суть этого бытия:

  1. число symbols, вероятно, будет намного меньше, чем число состояния.
  2. государства не осознают друг друга
  3. все переходы управляются из одной точки, поэтому в тот момент, когда вы хотите обработать символ DB_ERROR иначе, чем NETWORK_ERROR, вы просто меняете таблицу переходов и не касаетесь реализации состояний.

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

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

Легкий и гибкий. Регулярное хранение кода делает его читаемым и "формальный".

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

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

И последнее, но не менее важное: этот способ кодирования представляет собой точный перевод с языка state machine draw на любой язык, который вы будете использовать.

Я предпочитаю действительно простой подход для такого рода кода.

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

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

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

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

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

Эта библиотека может быть полезна для реализации машины состояний для управления вашим графическим интерфейсом: PTN Engine