Pull to refresh

StateController. Событийная модель в разработке интерфейсов. Часть 1

Reading time5 min
Views15K

Введение


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

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

Проблемы одностраничного подхода


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

Еще один подход, который требовал пересмотра, это модель построения самого модуля. Я старался уйти от MVC применительно к HTML, потому что он требовал существенного объема работы при весьма сомнительной эффективности. Модули должны быть слабосвязанными, не иметь никаких способов прямого общения между собой. Где хранить данные? А почему бы не использовать для этого структуры HTML! DOM предоставляет шикарные и удобные инструменты по работе со структурами данных, фактически являясь готовой Моделью.

Оставалось найти «нитки», которыми все это следовало сшить воедино. Единственный вариант, который мне пришел в голову, это использовать событийную модель. Так появился StateController v2.

Как работает событийная модель?


Построена она на двух основных компонентах: обработчики и генераторы событий. Сами события могут быть обезличеными и именоваными. Пример обезличенного события — вызов функции или метода
foo(1)

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

Пример именованого события — click. Тут все понятно, привычно, знакомо. Генератор запускает определенное событие, на которое отзываются какие-то обработчики.

Структурно-событийная модель приложения


Желание уйти от классического MVC в JS, в совокупности с требованием слабой межмодульной коммуникации, привело меня к следующим выводам:

Лучшее хранилище данных — DOM структура
Дабы не делать лишние синхронизации данных между JS и HTML, модификации проводим напрямую с DOM-структурой. Если все равно нужны данные в виде JS-объектов, собираем их на момент вызова методов во временные структуры, потом выкидываем. Сперва это кажется несколько нелогичным, ведь приходится по каждому чиху собирать информацию, однако поддержка консистентности совместно используемых данных обойдется дороже. Особенно в одностраничных приложениях, где нет никакой гарантии, что в данных момент времени в данном месте не появится еще какая-то DOM-ветка, которую загрузил один из модулей по требованию пользователя.

Лучшая связь между модулями — события
Научить модули генерировать определенные события гораздо проще, чем увязывать их обработчики между собой, чтобы при модификации данных одним модулем, автоматом проводить все необходимые действия в связанных модулях. Эдакий полиморфизм, единый API общения.

Ноды как носители обработчиков
Перекладывая на конечные ноды DOM-дерева «ответственность» за выполняемые действия, получаем еще одну абстракцию — сценарии. Лучше конкретной ноды никто не знает, что делать в тех или иных случаях. Таким образом структура DOM-документа несет логику работы. Вот тут кроется самое основное различие подходов событийных и селективных моделей фреймворков.

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

Вроде и не сильно отличается, скажете Вы. Давайте решим следующую задачу. Имеем три элемента формы: адрес регистрации (ra), адрес проживания (la), чекбокс «Адрес проживания совпадает с адресом регистрации» (ch). По умолчанию чекбокс находится в отмеченном состоянии, адрес проживания скрыт. Если пользователь снимает галочку, ему показываем поле с адресом проживания. При выключении чекбокса форма ввода не только скрывается, но и очищается от введенных пользователем данных. Ничего сложного.

Абстрактная реализация по селективной методике будет выглядеть примерно так:
  1. Повесить обработчик click на ch, который будет делать 2
  2. Если состояние checked, то 3, иначе 5
  3. Показать la
  4. Выход
  5. Скрыть la
  6. Очистить la
  7. Выход

Абстрактная реализация по событийной методике будет выглядеть так:
  1. Повесить обработчик события click на ch, который будет делать 2
  2. Если состояние checked, то 3, иначе 5
  3. Запустить событие equal_data
  4. Выход
  5. Запустить событие not_equal_data
  6. Выход


  1. Повесить обработчик equal_data на la, который будет делать 2
  2. Скрыть ноду
  3. Очистить форму
  4. Выход


  1. Повесить обработчик not_equal_data на la, который будет делать 2
  2. Показать ноду
  3. Выход

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

Позитивные моменты от событийного подхода


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

Принцип KISS в действии. Сценарий проще читать, чем код конечной реализации. Если обработчики сделать «тупыми», чтобы они выполняли какие-то конкретные действия, то допустить в них ошибку гораздо сложнее. Да и переносимость кода получается весьма впечатляющей, библиотеки обработчиков кочуют между проектами, иногда вместе со сценариями.

Негативые моменты


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

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

В следующей статье я перейду ближе к практической реализации.
Часть 2

Спасибо за внимание!
Tags:
Hubs:
Total votes 20: ↑13 and ↓7+6
Comments45

Articles