Конечные автоматы в JavaScript. Часть 1: Разработаем виджет (исходники)Источник: IBM developerWorks Россия Эдвард Принг
На протяжении нескольких лет web-дизайнеры без особого оживления использовали интерпретаторы JavaScript в популярных web-браузерах, чтобы улучшить внешний вид своих web-сайтов. Чаще всего для этого они копировали короткие фрагменты кода в свои web-страницы. Сейчас же, благодаря недавнему распространению технологий AJAX, инженеры по программному обеспечению стали, кроме того, использовать JavaScript для разработки нового поколения приложений, которые выполняются в web-браузере. Поскольку размер приложений на основе браузеров становится все больше, то для них все чаще требуются те же шаблоны проектирования и дисциплина разработки, которые характерны для других сред выполнения. Приложения на базе браузеров выполняются в среде реального времени, в которой события мыши, клавиатуры, таймера, сети и программ могут произойти в любой момент времени . Когда поведение управляемой событиями программы зависит от порядка, в котором происходят события, написание такой программы становится очень запутанным, а значит, и более сложным для отладки и внесения изменений. Инженеры по программному обеспечению давно используют конечные автоматы в качестве организующего принципа при разработке управляемых событиями программ. Дисциплина, которая диктуется конечными автоматами, придает проекту строгость, заменяя запутанную логику простыми таблицами, в результате программы становятся проще в реализации и в тестировании. По традиции, конечные автоматы считаются полезными в разработке таких непохожих программ, как сетевые драйверы и компиляторы. В равной степени они могут быть полезны при разработке приложений на базе браузеров. В этой статье вам будет предложено разработать простое приложение-конечный автомат в качестве упражнения, позволяющего изучить некоторые особенности языка JavaScript:
Эти функции предоставляют лаконичный и компактный способ организовать действия для событий и переходов между состояниями, а также изящный способ справиться с различиями между моделями обработки событий в браузерах. Пример приложения, Постепенно появляющаяся и постепенно исчезающая подсказка - это более совершенный элемент, чем встроенные подсказки большинства браузеров. Подсказки, созданные при помощи виджета Fading Tooltip (Постепенно появляющаяся и исчезающая подсказка) используют анимацию, чтобы постепенно появляться, а затем постепенно исчезать, вместо того, чтобы внезапно выскакивать и внезапно пропадать; кроме того, они перемещаются вслед за курсором. Шаблон конечного автомата, который используется для разработки этого поведения, делает его логику прозрачной. Особенности языка JavaScript, который используется для реализации приложения, делают исходный код компактным и эффективным. В этой статье показано, как спроектировать поведение анимированного виджета при помощи табличного и графического представления конечного автомата. В следующих статьях серии речь пойдет о том, как реализовать табличное представление конечного автомата на языке JavaScript, и что делать с практическими проблемами тестирования реализации виджета в популярных браузерах. Обычные подсказкиБольшинство современных приложений с графическим интерфейсом могут кратковременно отображать маленькие текстовые окна, содержащие полезные определения, инструкции или рекомендации, когда курсор останавливается над некоторыми визуальными элементами управления, такими как кнопка, поле выбора или поле ввода. Эти полезные текстовые окна получили название "balloon help" (всплывающие пояснения) в ранних системах Apple. В некоторых продуктах IBM они назывались infopops (всплывающей информацией), а в некоторых продуктах Microsoft - ScreenTips (экранными подсказками). В этой статье используется более общий термин подсказка. Такие популярные web-браузеры, как Netscape Navigator, Microsoft Internet Explorer, Opera и Mozilla Firefox отображают подсказки для любого HTML- элемента, который имеет атрибут Листинг 1. HTML-код для отображения подсказок браузером
Страница примеров показывает, как ваш браузер интерпретирует HTML-элементы с атрибутом Более совершенные подсказкиВстроенные подсказки имеют много возможностей для улучшений, а последние версии популярных браузеров предоставляют все исходные ингредиенты для создания более совершенных подсказок. TML-элемент Division создает текстовое окно, которое можно расположить в любом окне браузера. Почти любой аспект внешнего вида этого окна можно определить с помощью каскадных таблиц стилей (CSS). Перемещения курсора, запрограммированные на языке JavaScript, могут запускать различные действия для любых видимых элементов в окне браузера. Для организации последовательности этих действий можно программировать таймеры. На странице примеров показаны также некоторые HTML-элементы с более совершенными подсказками. Если у вас последняя версия одного из популярных браузеров, вы можете сравнить более совершенные подсказки со встроенными подсказками:
Это усовершенствованное поведение и внешний вид не является только косметическим улучшением; оно способствует повышению удобства использования. Когда пользователи попадают на страницы, насыщенные тысячами элементов, они могут не заметить подсказки, которая внезапно появляется на экране. Человеческое зрение, которое особенно чувствительно к движущимся объектам, с большей вероятностью заметит подсказку, которая постепенно появляется в поле зрения и движется вместе с курсором, даже если внимание растерявшегося пользователя сфокусировано в другом месте страницы. Изображения, форматирование и стилевое оформление могут более эффективно преподносить информацию, чем неформатированный текст. Кроме того, все параметры этих более совершенных подсказок являются настраиваемыми. Далее в статье рассказывается о проектировании виджета исчезающей подсказки FadingTooltip в виде конечного автомата. В следующих статьях серии будет рассказано о том, как реализовать и протестировать программу. Конечные автоматыКонечные автоматы моделируют поведение, при котором реакции на будущие события зависят от предыдущих событий. Существует огромный пласт научной литературы по этой теме, но удобное рабочее определение очень простое. Конечный автомат - это компьютерная программа, которая состоит из:
Конечные автоматы наиболее полезны в ситуациях, в которых поведение управляется многими различными типами событий, а реакция на определенное событие зависит от последовательности предыдущих событий. События, которые управляют конечными автоматами, могут быть внешними по отношению к компьютеру и исходить от клавиатуры, мыши, таймера или сетевой активности, или внутренними, исходящими от других компонентов прикладной программы или от других приложений. Состояния - это метод запоминания предыдущих событий, а переходы - метод организации реагирования на будущие события. Одно из событий должно быть помечено как исходное состояние. Может существовать также конечное состояние, но это необязательно, и у виджета FadingTooltip конечного состояния нет. Существует два распространенных представления конечных автоматов:
Эти представления эквивалентны, но делают акцент на разных аспектах проекта. Оба представления полезны и используются в данной статье. Разработка управляемых событиями программ с помощью конечных автоматов несколько сложнее обычного процедурного программирования; такая разработка требует больше дисциплины в общем и больше проектной работы в частности. При хорошем исполнении в результате можно получить более простой программный код, меньшую продолжительность тестирования и облегченное сопровождение. Несмотря на это, сложность подхода конечных автоматов оправдана не для всех управляемых событиями программ. Если, например, диапазон событий невелик, или действия, запускаемые событиями, всегда одни и те же, дополнительные затраты ресурсов на разработку могут не оправдаться. Конечные автоматы и среда выполненияКонечные автоматы управляются событиями и нуждаются в способе привязки к событиям, о которых идет речь, в их среде выполнения. Такие привязки, которые называются обработчиками событий , представляют собой очень небольшие фрагменты кода, вставленные в среду выполнения так, чтобы они могли выполняться при наступлении конкретного события. Для выполнения обработчики событий нуждаются в следующей базовой информации:
Язык JavaScript прекрасно подходит для построения управляемых событиями конечных автоматов. На самом деле, JavaScript, возможно, подходит для этого даже слишком хорошо -- в языке возможны три разных способа привязки событий. Каждая из этих моделей событий не отличается сложностью, однако, чтобы программы могли выполняться в любом популярном браузере, они должны реализовать все три модели. Контекст события передается непосредственно обработчикам событий по двум из этих моделей событий; для еще одной модели охватить контекст события обработчиком этого события позволяют замыкания функций JavaScript. JavaScript предоставляет объектную модель , которая может показаться несколько необычной программистам на Java и C++, тем не менее, она полностью пригодна для программирования переменных и методов конечных автоматов. Кроме того, ассоциативные массивы JavaScript позволяют напрямую программировать представления конечных автоматов в виде двумерных таблиц. Методичная разработка поведенияОсновные ингредиенты для конечного автомата - это события, на которые он реагирует, и состояния, в которых он ожидает между событиями. Проект должен предусматривать все возможные события для всех возможных состояний:
Начнем проектирование процедуры с графа на рисунке 1, на котором состояния показаны в виде эллипсов, а переходы - в виде стрелок, соединяющих эллипсы, и закончим таблицей, показанной на рисунке 4, в которой события и состояния перечисляются соответственно в заголовках строк и столбцов. В каждой ячейке таблицы перечислены действия, которые должны выполняться, когда в определенном состоянии произойдет определенное событие, или показано, что данное событие не может наступить в данном состоянии. Обычно требуется несколько итеративных повторений этой проектной процедуры, чтобы получить граф или таблицу без ошибок. Если конечный автомат имеет много событий и состояний, то процедура может быть довольно утомительной, поэтому от разработчика требуется определенная дисциплина, чтобы методично работать над каждой ячейкой таблицы в процессе каждой итерации. Благодаря этому вы сможете продумать, какое поведение желаете видеть во всех возможных ситуациях. Возможно, вы увидите пути для дальнейшего усовершенствования или детализации поведения, возможно, откроете, что понадобится больше состояний, чем предполагалось первоначально, а может быть, придется перетасовать действия между ячейками, чтобы определить правильное поведение в каждой ситуации. Эта методичная процедура проектирования конечных автоматов оправдана, хотя и утомительна. Заполненная таблица, которую вы видите на рисунке 4, показывает всю логику поведения и может быть переведена непосредственно в программный код (см. исходный код actionTransitionFunctions). О возможностях JavaScriptДля разработки виджета FadingTooltip нужно познакомиться с некоторыми возможностями языка JavaScript. В духе проектирования сверху вниз, я кратко обрисую здесь основные идеи, а подробности реализации выделю в следующей статье этой серии. Все популярные браузеры способны передавать события коду JavaScript, когда курсор проходит над HTML-элементом на web-странице. Эти события называются mouseover , mousemove и mouseout ; названия показывают, что курсор перемещается над, движется в пределах и смещается за пределы HTML-элемента. Браузер передает текущие координаты положения курсора вместе с этими событиями. При наступлении события программа на JavaScript может динамически создавать HTML-элементы Division, вносить в них текст, изображения и разметку, и помещать их рядом с курсором. Браузеры не имеют встроенных функций постепенного появления и исчезновения, но их можно имитировать, изменяя степень прозрачности (ну, на самом деле, непрозрачности, которая противоположна прозрачности) элемента Division с течением времени. В JavaScript есть два типа таймеров: разовые таймеры, которые генерируют событие timeout по истечении установленного времени, и тикеры, которые генерируют события timetick периодически. Для виджета FadingTooltip нам понадобятся оба этих типа. Рисуем набросок графа состоянияНачнем проектирование с рассмотрения базового поведения, которое нужно запрограммировать для виджета FadingTooltip. При прохождении курсора над определенным HTML -элементом нам нужно, чтобы виджет ждал остановки курсора над этим элементом. Если курсор остановился над элементом, то виджет должен, постепенно усиливая, вывести подсказку на экран, мгновение подержать ее на экране, а затем, постепенно ослабляя, удалить ее с экрана. Нашему конечному автомату придется реагировать на такие события:
Мы создадим некоторые состояния, в которых конечный автомат будет ожидать между событиями. Давайте назовем исходное состояние виджета Inactive; это такое состояние, при котором он будет ожидать активации событием mouseover. В состоянии Pause виджет будет ожидать, пока событие timeout не покажет, что курсор задержался над HTML-элементом на достаточно продолжительное время. Затем, в состоянии FadeIn, виджет будет ожидать, пока выполняется анимирование эффекта постепенного появления событиями timetick, и далее, в состоянии Display, ожидать наступления следующего события timeout. Наконец, в состоянии FadeOut виджет будет ожидать, пока выполняется анимирование эффекта постепенного исчезновения следующими событиями timetick. После этого виджет возвращается в состояние Inactive, в котором он будет ожидать следующего события mouseover. На рисунке 1 этот процесс отображен в виде графа, на котором состояния отображены в виде эллипсов, переходы в виде соединяющих эти эллипсы стрелок, а события - как надписи над стрелками. Двойной обводкой выделен эллипс исходного состояния. Рисунок 1. Первоначальный набросок графа состояний Виджету FadingTooltip нужно выполнять некоторые действия для каждого события, которое он обработает.
На рисунке 2 эти действия показаны под событиями, которые их запускают. Рисунок 2. Первоначальный набросок графа состояний, на котором события дополнены действиями Перевод графа состояния в таблицу состоянияПредставление в виде графа, показанное выше - хороший способ начать проектирование конечного автомата, но представление в виде таблицы лучше подходит для завершения проектирования, поскольку позволяет наглядно отобразить все комбинации событий и состояний. Чтобы перевести граф состояний в таблицу состояний, в качестве заголовков строк выберите названия событий, а в качестве заголовков столбцов - названия состояний. Порядок названий может быть произвольным; я поместил исходное состояние в первый столбец, а инициирующее событие - в первую строку. Затем скопируйте действия и следующее состояние для каждого события в соответствующую ячейку таблицы, как показано на рисунке 3. Рисунок 3. Первоначальная таблица состояний, соответствующая графу состояний Заполняем таблицу состоянийЧтобы завершить проектирование нашего конечного автомата, необходимо продумать, как заполнить каждую пустую ячейку в этой таблице. Нужно для каждой ячейки решить, может ли данное событие произойти в этом состоянии, и если может, то какие действия должен выполнить виджет в этой ситуации и каким должно быть следующее состояние. Это утомительная, но совершенно необходимая фаза процесса проектирования. Порядок заполнения ячеек не имеет значения. Обычно к этому шагу в процессе разработки возвращаются неоднократно, еще и еще раз продумывая каждую ячейку, часто пересматривая ее содержимое, причем каждый раз по-новому. Нередки также добавления (или удаления) состояний по ходу процесса, что вызывает дальнейший пересмотр. В данной статье я не буду останавливаться на этих итерациях, подводя итог всему этапу проектирования и пересматривая результаты для всех состояний и событий по очереди.
На рисунке 4 показаны все эти дополнительные действия и переходы. Ячейки, оставшиеся пустыми, соответствуют ситуации "не должно наступить". Рисунок 4. Таблица состояний для виджета FadingTooltip widget после разработки Табличное представление конечного автомата всегда может быть переведено в графическое, поскольку эти представления эквивалентны друг другу. На рисунке 5 показано графическое представление заполненной таблицы. Рисунок 5. Граф состояний для виджета FadingTooltip после разработки Составление списка переменных состоянияПосле составления таблицы и графа состояния полезно пересмотреть их еще раз, чтобы составить список переменных, которые автомату нужно будет запоминать между событиями, чтобы он мог выполнять родственные действия в разных ячейках. Нашему конечному автомату понадобятся переменные состояния для всего, что перечислено в листинге 2. Листинг 2. Первоначальный список переменных состояний
Хотя переменные JavaScript являются нетипизированными, значения, которые они содержат, типизированы (то есть, значения любого типа могут быть присвоены любой переменной). В этом же духе я выберу имена для переменных состояния и, в примечаниях, укажу типы, которые я собираюсь им присвоить. Все готово для реализации проектаТеперь у нас есть заполненная таблица состояний и список переменных состояний, и мы готовы реализовать конечный автомат. Не пропустите следующую статью, в которой рассказывается о том, как это сделать. Однако не забывайте, что разработка - это итеративный процесс; возможно, придется вернуться к фазе проектирования... |