Реализация Fsm

В статье описана разработанная автором миниатюрная Java-библиотека, позволяющая кратко и понятно определять конечные автоматы.

Библиотека, назовем ее AkerFSM, доступна в Google-код .

В первой части статьи сформулированы предпосылки и требования к библиотеке.

Во второй части представлен абстрактный пример использования библиотеки.

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

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

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

Пример кода может быть посмотреть здесь , а сам подход, фигурирующий под названиями «SWITCH-технология» и «Автоматическое программирование», подробно описан в статьях Шалыто и Туккеля на упомянутом сайте.

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

Поэтому я решил создать реализацию, отвечающую следующим требованиям:

  • машина указана так же четко и лаконично, как и при использовании оператора-переключателя
  • реализация объектно-ориентированная
  • реализация поддерживает все возможности автоматического программирования
Забегая вперед, отмечу, что результат превзошел первоначальную постановку задачи (подробнее в конце статьи).

Абстрактный пример В качестве первого примера рассмотрим абстрактное окно с экранной формой.

Окно может быть открытым или закрытым.

Если окно открыто, данные формы можно сохранить.

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

Для начала давайте объявим наш автомат и определим набор его состояний:

Реализация FSM

Теперь определим сам автомат и метод, который его создаст:

Реализация FSM

Чтобы использовать машину, остается лишь создать ее объект и вызвать метод handleEvent() для обработки событий, передав событие в качестве параметра:

Реализация FSM

Таким образом, предложенная реализация позволяет описывать автоматы так же четко и лаконично, как и в случае с оператором переключения — для определения состояния требуется несколько строк кода, а все определение автомата помещается в один блок.

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

Если ваша среда позволяет отключить автоматическое форматирование для данного фрагмента текста, то вам повезло.

Бонус Использование перечисления делает определение автомата гораздо более понятным, чем кодирование состояний с помощью порядковых номеров или констант. Кстати, в именах констант в enum можно использовать кириллицу
Особенности устройства библиотеки Основными классами библиотеки являются State и FSM, назначение которых очевидно.

Оба класса можно легко расширить, как будет показано ниже.

Классы объявляются следующим образом:

Реализация FSM



Реализация FSM

Универсальный STATES определяет перечисление, в котором хранятся многие состояния машины.

Общий тип EVENT определяет класс событий, обрабатываемых машиной.

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

В рассматриваемых примерах для простоты используется String. Три метода класса State: enter(), handleEvent() и exit() предназначены для переопределения при создании определенных состояний.

Enter вызывается, когда автомат входит в рассматриваемое состояние, handleEvent вызывается, когда событие обрабатывается, и выход соответственно, когда автомат выходит из рассматриваемого состояния.

Эти методы реализуют шаблон «Метод шаблона», поэтому при их переопределении вызов super не требуется.

Вместе с классом State при определении автомата может использоваться его потомок — класс SuperState (State и SuperState вместе реализуют аналог паттерна Composite).

Целью SuperState является реализация общего поведения для группы состояний, например группового перехода.

Метод State.toString() возвращает имя и порядковый номер состояния (да здравствует перечисление!) Методы FSM.toString() и SuperState.toString() определяются аналогично.

Поведение класса FSM при вызове метода handleEvent() следующее:

  • вызов handleEvent() текущего состояния и групп текущего состояния
  • определить следующее состояние (групповые переходы имеют приоритет над обычными)
  • вызовите exit() для текущего состояния и всех состояний группы, которые останутся во время перехода
  • вызвать enter() для нового состояния и всех состояний группы, в которые мы входим во время перехода
  • выполнять переходы до тех пор, пока результат определения следующего состояния не станет нулевым
Кроме того, FSM реализует 14 событий, для которых вы можете назначать обработчики, переопределяя либо определенные методы обработчика событий, либо метод, вызываемый при возникновении любого из событий.

Примером использования обработчиков событий являются классы MonitoredFSM и LoggedFSM. Лучший способ более подробно понять поведение классов библиотеки — запустить тесты JUnit, поставляемые с библиотекой, в отладчике.

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

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

Давайте создадим приложение GWT, которое отображает форму на экране при нажатии кнопки.

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

Форму можно закрыть нажатием кнопки.

Процесс загрузки может быть прерван пользователем.

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

У машины будет пять состояний:

Реализация FSM

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

Первый будет включать состояния LOADCONFIG и LOADDATA и будет служить для обработки события «RPCFailure».

Второй будет включать состояния LOADCONFIG, LOADDATA и SHOW и будет служить для обработки события «HideEvent».

Остаётся только реализовать прогресс-бар.

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

Во-первых, вместо FSM мы используем класс MonitoredFSM, реализующий паттерн Observer. Это позволит нам подключить собственный обработчик событий для изменения состояния машины (другой способ — самостоятельно переопределить метод onAfterSwitchState() класса FSM).

Во-вторых, давайте создадим класс ProgressState из класса State. От своего предка он будет отличаться наличием описания состояния, указанного в конструкторе.

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

Реализация FSM

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

Когда вы вернетесь в нормальное состояние, индикатор прогресса исчезнет. Как видите, я немного схитрил с отображаемыми цифрами текущего состояния и общим количеством состояний ("было бы отображено 2/5" и "3/5", хотя это было бы более логично с точки зрения пользователя).

просмотр для отображения «1/2» и «2/2»).

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

Собрав все вместе, получаем следующее определение автомата (в состояниях LOADCONFIG и LOADDATA иллюстрируются разные способы обработки внешних воздействий):

Реализация FSM

Обработчики событий нажатия кнопок будут выглядеть так:

Реализация FSM

Обработчики запросов RPC будут выглядеть так:

Реализация FSM

Как следует из двух последних фрагментов кода, мы одним махом справились с задачей аккуратной реализации асинхронной части GWT-приложения, создав для этого простой и универсальный паттерн.

Причем этот шаблон идентичен как для обработчиков RPC-запросов, так и для обычных обработчиков событий.

Исходные коды библиотеки доступны по адресу Google-код .

В папке «trunk/fsm» находится сама библиотека и тесты JUnit, а в папке «trunk/gwt_fsm» — рассматриваемое в качестве примера приложение GWT. выводы Библиотека AkerFSM поддерживает все возможности автоматического программирования:

  • явный выбор и кодирование конечных автоматов
  • четкое и краткое определение конечного автомата, внешне похожее на оператор переключения
  • поддержка разных типов игровых автоматов (Мили, Мура, смешанные)
  • поддержка событийных и вычислительных машин (и с возможностью смешивания этих двух моделей в одной машине)
  • поддержка вложенных автоматов
  • поддержка групповых переходов
  • возможность логирования в терминах конечного автомата
Помимо поддержки перечисленных возможностей автоматического программирования, библиотека AkerFSM имеет два фундаментальных преимущества:
  1. Бизнес-логика отделена от технологического кода.

    Действительно, логика работы конкретной машины (бизнес-логика) задается отдельно от кода, реализующего саму модель конечного автомата.

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

  2. Библиотека является объектно-ориентированной, а базовые классы библиотеки легко расширяются.

Благодаря этим преимуществам появляется множество преимуществ и возможностей для расширения.

Перечислим некоторые из них

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

  2. В отличие от упомянутых выше публикаций по автоматическому программированию, в которых «групповой переход» присутствует лишь как элемент графической записи и никак не поддерживается в реализации, библиотека AkerFSM полностью реализует концепцию «группового состояния».

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

  3. Переход от модели конечного автомата к модели сети Петри осуществляется путем простого расширения класса автоматов.

  4. Также можно просто создать модель автомата, способную хранить историю переходов.

  5. Реализация паттерна «Стратегия» совместно с классом FSM позволяет отделить автомат от класса, которым он управляет. Это, в частности, дает возможность проводить JUnit-тестирование автомата, полностью изолируя его от остального приложения.

  6. Класс State (вместе с классом FSM) реализует шаблон State. Самый простой вариант использования его в этой роли требует лишь определить интерфейс с специфичными для вашей задачи операциями и реализовать этот интерфейс в необходимом количестве потомков класса State. Хотя «Банда четырех» в описании паттерна State пишет, что определение логики перехода внутри конкретных состояний вводит «зависимости реализации между подклассами», архитектура библиотеки AkerFSM лишена этого недостатка.

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

  7. Возможно расширение класса FSM, что позволит изменять определение автомата во время выполнения (добавлять, удалять и заменять состояния).

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

Полкарпова, Шалыто «Автоматическое программирование».

.

ПС.

Я уверен, что подобную реализацию можно сделать еще красивее в Groovy или Ruby. Теги: #fsm #Конечный автомат #конечный автомат #Автоматическое программирование #java #gwt #шаблоны #Разработка веб-сайтов

Вместе с данным постом часто просматривают: