В статье описана разработанная автором миниатюрная Java-библиотека, позволяющая кратко и понятно определять конечные автоматы.
Библиотека, назовем ее AkerFSM, доступна в Google-код .
В первой части статьи сформулированы предпосылки и требования к библиотеке.
Во второй части представлен абстрактный пример использования библиотеки.
В третьей части обсуждаются важные аспекты структуры самой библиотеки.
Четвертая часть посвящена упрощенному примеру из реальной жизни, в котором конечный автомат используется для определения поведения одного из контроллеров в приложении GWT. Предварительные условия Существуют различные способы реализации конечного автомата.
До создания предлагаемой библиотеки я считал наиболее удачным методом использование оператора переключения, осуществляющего выбор на основе текущего номера состояния.
Пример кода может быть посмотреть здесь , а сам подход, фигурирующий под названиями «SWITCH-технология» и «Автоматическое программирование», подробно описан в статьях Шалыто и Туккеля на упомянутом сайте.
Несмотря на свою простоту, реализация оператора переключения совершенно не вписывается в объектно-ориентированную парадигму и из-за этого не позволяет в полной мере использовать возможности современных языков.
Поэтому я решил создать реализацию, отвечающую следующим требованиям:
- машина указана так же четко и лаконично, как и при использовании оператора-переключателя
- реализация объектно-ориентированная
- реализация поддерживает все возможности автоматического программирования
Абстрактный пример В качестве первого примера рассмотрим абстрактное окно с экранной формой.
Окно может быть открытым или закрытым.
Если окно открыто, данные формы можно сохранить.
Прежде чем окно отобразится в первый раз, его необходимо инициализировать.
Для начала давайте объявим наш автомат и определим набор его состояний:
Теперь определим сам автомат и метод, который его создаст:
Чтобы использовать машину, остается лишь создать ее объект и вызвать метод handleEvent() для обработки событий, передав событие в качестве параметра:
Таким образом, предложенная реализация позволяет описывать автоматы так же четко и лаконично, как и в случае с оператором переключения — для определения состояния требуется несколько строк кода, а все определение автомата помещается в один блок.
Использование стандартного форматирования исходного кода, как в случае с оператором switch, немного ухудшит картину, но здесь придется выбирать.
Если ваша среда позволяет отключить автоматическое форматирование для данного фрагмента текста, то вам повезло.
Бонус | Использование перечисления делает определение автомата гораздо более понятным, чем кодирование состояний с помощью порядковых номеров или констант. Кстати, в именах констант в enum можно использовать кириллицу |
Оба класса можно легко расширить, как будет показано ниже.
Классы объявляются следующим образом:
Универсальный 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() для нового состояния и всех состояний группы, в которые мы входим во время перехода
- выполнять переходы до тех пор, пока результат определения следующего состояния не станет нулевым
Примером использования обработчиков событий являются классы MonitoredFSM и LoggedFSM. Лучший способ более подробно понять поведение классов библиотеки — запустить тесты JUnit, поставляемые с библиотекой, в отладчике.
Эти тесты были специально написаны таким образом, чтобы быть примером использования библиотеки и иллюстрацией логики ее работы.
Бонус | Исходные коды библиотеки нормально компилируются с помощью GWT. |
Давайте создадим приложение GWT, которое отображает форму на экране при нажатии кнопки.
При отображении формы необходимо загружать данные с сервера в два этапа - сначала загружается конфигурация формы, а затем загружаются данные, которые будут отображаться на форме.
Форму можно закрыть нажатием кнопки.
Процесс загрузки может быть прерван пользователем.
В процессе загрузки должен отображаться индикатор прогресса с названием выполняемой операции.
У машины будет пять состояний:
Помимо обычных состояний, мы также определяем два групповых состояния.
Первый будет включать состояния LOADCONFIG и LOADDATA и будет служить для обработки события «RPCFailure».
Второй будет включать состояния LOADCONFIG, LOADDATA и SHOW и будет служить для обработки события «HideEvent».
Остаётся только реализовать прогресс-бар.
Это можно сделать очень элегантным и универсальным способом, который подойдет не только для нашего абстрактного приложения, но и для большинства подобных задач.
Во-первых, вместо FSM мы используем класс MonitoredFSM, реализующий паттерн Observer. Это позволит нам подключить собственный обработчик событий для изменения состояния машины (другой способ — самостоятельно переопределить метод onAfterSwitchState() класса FSM).
Во-вторых, давайте создадим класс ProgressState из класса State. От своего предка он будет отличаться наличием описания состояния, указанного в конструкторе.
В-третьих, давайте определим обратный вызов для нашего экземпляра класса MonitoredFSM следующим образом:
Каждый раз, когда вы вводите состояние типа ProgressState, этот обратный вызов отображает индикатор прогресса, который будет указывать описание текущего состояния, номер текущего состояния и общее количество состояний.
Когда вы вернетесь в нормальное состояние, индикатор прогресса исчезнет. Как видите, я немного схитрил с отображаемыми цифрами текущего состояния и общим количеством состояний ("было бы отображено 2/5" и "3/5", хотя это было бы более логично с точки зрения пользователя).
просмотр для отображения «1/2» и «2/2»).
Я не буду описывать возможные решения этой проблемы, скажу лишь, что класс EnumSet и его статические методы могут оказаться полезными.
Собрав все вместе, получаем следующее определение автомата (в состояниях LOADCONFIG и LOADDATA иллюстрируются разные способы обработки внешних воздействий):
Обработчики событий нажатия кнопок будут выглядеть так:
Обработчики запросов RPC будут выглядеть так:
Как следует из двух последних фрагментов кода, мы одним махом справились с задачей аккуратной реализации асинхронной части GWT-приложения, создав для этого простой и универсальный паттерн.
Причем этот шаблон идентичен как для обработчиков RPC-запросов, так и для обычных обработчиков событий.
Исходные коды библиотеки доступны по адресу Google-код .
В папке «trunk/fsm» находится сама библиотека и тесты JUnit, а в папке «trunk/gwt_fsm» — рассматриваемое в качестве примера приложение GWT. выводы Библиотека AkerFSM поддерживает все возможности автоматического программирования:
- явный выбор и кодирование конечных автоматов
- четкое и краткое определение конечного автомата, внешне похожее на оператор переключения
- поддержка разных типов игровых автоматов (Мили, Мура, смешанные)
- поддержка событийных и вычислительных машин (и с возможностью смешивания этих двух моделей в одной машине)
- поддержка вложенных автоматов
- поддержка групповых переходов
- возможность логирования в терминах конечного автомата
- Бизнес-логика отделена от технологического кода.
Действительно, логика работы конкретной машины (бизнес-логика) задается отдельно от кода, реализующего саму модель конечного автомата.
В этом случае реализацию модели можно переопределить и расширить независимо от конкретных автоматов, «украсив» бизнес-логику необходимыми технологическими операциями.
- Библиотека является объектно-ориентированной, а базовые классы библиотеки легко расширяются.
Перечислим некоторые из них
- Возможность определить в одном блоке кода действия, выполняемые при проверке условий перехода, при входе в состояние и при выходе из него.
- В отличие от упомянутых выше публикаций по автоматическому программированию, в которых «групповой переход» присутствует лишь как элемент графической записи и никак не поддерживается в реализации, библиотека AkerFSM полностью реализует концепцию «группового состояния».
Для группового состояния, как и для обычного состояния, можно определить условия перехода, а также действия, выполняемые при входе и выходе из группового состояния.
- Переход от модели конечного автомата к модели сети Петри осуществляется путем простого расширения класса автоматов.
- Также можно просто создать модель автомата, способную хранить историю переходов.
- Реализация паттерна «Стратегия» совместно с классом FSM позволяет отделить автомат от класса, которым он управляет. Это, в частности, дает возможность проводить JUnit-тестирование автомата, полностью изолируя его от остального приложения.
- Класс State (вместе с классом FSM) реализует шаблон State. Самый простой вариант использования его в этой роли требует лишь определить интерфейс с специфичными для вашей задачи операциями и реализовать этот интерфейс в необходимом количестве потомков класса State. Хотя «Банда четырех» в описании паттерна State пишет, что определение логики перехода внутри конкретных состояний вводит «зависимости реализации между подклассами», архитектура библиотеки AkerFSM лишена этого недостатка.
В нашем случае логика переходов между состояниями определяется конечным автоматом и записана в конкретных классах состояний, но зависимости между ними нет.
- Возможно расширение класса FSM, что позволит изменять определение автомата во время выполнения (добавлять, удалять и заменять состояния).
Полкарпова, Шалыто «Автоматическое программирование».
.
ПС.
Я уверен, что подобную реализацию можно сделать еще красивее в Groovy или Ruby. Теги: #fsm #Конечный автомат #конечный автомат #Автоматическое программирование #java #gwt #шаблоны #Разработка веб-сайтов
-
Прежде Чем Отправить Статью: Важные Советы
19 Oct, 24 -
Сохранить Gmail
19 Oct, 24 -
Интернет-Журнал Для Школ И Колледжей.
19 Oct, 24 -
Еще Один Взгляд На Парадокс Ферми
19 Oct, 24 -
Канобувости 11 Выпуск
19 Oct, 24 -
Вместо Патентов — Призовой Фонд
19 Oct, 24