Pull to refresh

Использование паттернов в разработке архитектуры флекс-приложения

Reading time6 min
Views2.7K

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

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


Сначала планировалось, что в основе интерфейса тула будут две таблицы: сотрудники и проекты. Можно перетаскивать мышью сотрудников на проекты, таким образом, назначая рабочие часы.

image

Приложение решено было писать на Флексе (+ AIR runtime). Фреймворк содержит все, что нам необходимо. Компонент AdvancedDataGrid служит основой для таблиц сотрудников и проектов. Он очень гибок благодаря рендерерам и имеет удобно подключаемую поддержку drag-n-drop. AIR обладает поддержкой многооконных приложений и умеет работать с файлами.

Для начала выделим основные требования.

Основные требования


1. Гибкость настройки view
С самого начала разработки в голову начали приходить идеи по улучшению удобства интерфейса. Группировать проекты по заказчикам, добавить возможности перетаскивания связей между проектами, перетаскивания сотрудников и проектов по папкам, потом появилось пожелание открывать несколько документов одновременно в разных окнах. Стало ясно, что необходимо как можно лучше отделить представление от управляющей логики и обеспечить гибкость его настройки.

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

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

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

1. Гибкость настройки view

Используем схему, подобную MVC. У нас есть 3 составляющие аналогичные model, view и controller: DataBuilder, View и Engine.
DataBuilder (model) хранит данные и обеспечивает базовую логику работы с ними. Только он знает, как создать элемент данных из начального набора параметров. DataBuilder’ы используются всеми engine’ами как фабрики элементов данных. Это обеспечивает правильное создание элементов с помощью одного и того же метода.
View имеет доступ к данным dataBuilder'ов. Для удобства эти данные храняться в статических переменных dataBuilder’ов, и доступны отовсюду. Также view обновляется по команде своего engine’а и отображает данные так, как ему следует. Кроме того, view посылает команды engine'ам в ответ на действия пользователя.
Engine (controller) занимается бизнес-логикой.
Получается следующая схема работы:

Таким образом, имеется 2 тройки dataBuilder-engine-view: для сотрудников и для проектов. Также в архитектуре присутствуют системы, где view или dataBuilder отсутствуют. Про это будет написано ниже.

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

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

Реализуем паттерн Chain of responsibility. Энджины будут соединены в цепь. Первому энджину в цепи отправляются команды, которые продвигаются по цепи до своего адресата.

Сначала добавим каждому энджину идентификатор public static const TYPE:String. Все идентификаторы должны отличаться. Их уникальность проверяется в фабрике энджинов, через которую они должны создаваться.

Команды будут передаваться с помощью класса CommandEvent, унаследованного от Event. У него есть поле commandType:String, в которое передается тип энджина, которому адресуется эта команда. Также ивент имеет набор полей для уточнения команды, чтобы энджин мог производить разнообразные действия. Например, добавление элемента, изменение некоторого его свойства, обновление своего view, чтение или запись в файл.

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

— В next хранится ссылка на следующий элемент в цепи.
— Метод activate(event) абстрактный. Он переопределяется в подклассах, где реализуется конкретное поведение для каждого энджина.
— Метод handle(event) производит проверку, обрабатывает ли данный энджин команду с пришедшим типом. Если да, выполняется activate(), если нет, вызывается метод handle() следующего в цепи энджина.

Таким образом, чтобы передать команду в цепь надо лишь вызвать handle() у корневого элемента. Для того чтобы цепь заработала, в Application корневой engine подписывается на CommandEvent:
rootEngine.addEventListener(CommandEvent.ACTIVATE, rootEngine.handle);


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



Как уже говорилось, имеется 2 тройки engine-dataBuilder-view: для сотрудников и для проектов.

Есть система без данных: CommandPanel (view) и CommandEngine к ней. Это панель в верхней части каждого окна. Она занимается только отправкой команд.

Есть система без view: для объектов, определяющих связи между сотрудником и проектом. Это пара BindsBuilder-BindsEngine. Представление связей входит как в EmployeesView, так и в ProjectsView.

Наконец, есть несколько engine’ов без представления и данных: InputOutputEngine (работа с XML-файлами), CSVEngine (работа с таблицами), ShortCutsEngine (работа с горячими клавишами).

Такая архитектура позволяет легко добавить новые блоки. Например, сначала не предполагалось делать поддержку внешних форматов. Но к концу разработки тула выявилась необходимость добавить функцию экспорта/импорта CSV. Для этого нужно добавить CSVEngine в цепочку. Он почти такой же, как и InputOutputEngine, только в содержит другой парсер данных. Затем добавляем кнопки на Command Panel с отправкой соответствующих событий. Теперь приложение умеет работать с CSV!

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

Достоинства нашей архитектуры

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

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

Еще одним достоинством такой архитектуры является упрощение процесса разработки. Логика работы каждого engine’а инкапсулирована. Благодаря этому, над одним приложением может работать сразу несколько людей без появления конфликтов.

Очень легко осуществляется отправка команд в цепочку от объектов дисплей-листа. Event с bubbles == true всплывает до Application и попадает в цепочку. Вот схема работы, где предполагается, что пользователь нажимает на кнопку в одном из view:

3. Поддержка истории и горячих клавиш

Как же в современном ПО без возможности отмены последних действий?
Архитектура позволяет легко добавить поддержку истории.

Сначала история была сделана на основе паттерна Memento. Состояние этого приложения после каждого действия однозначно преобразуется в xml-объект, который можно сохранить в Memento. Но оказалось, что в случае реальных данных полное обновление таблиц по xml происходит слишком медленно, около 1-2 секунд. Поэтому пришлось использовать более сложный способ: создавать и запоминать обратные действия к каждому из действий пользователя, а затем последовательно их вызывать при нажатии undo.

Менеджер истории хранит наборы прямых и обратных действий в двух массивах. Когда энджины получают команды, они добавляют прямые и обратные им команды в историю. Теперь при нажатии undo или redo достаточно взять нужную команду из массива и вызвать с ней dispatchEvent(). Все энджины воспримут ее как обычную команду, пришедшую из view, и выполнят соответствующие действия.

Работа с CommandEvent очень напоминает паттерн Command. Разница в том, что в паттерне «receiver» инкапсулирован в команду, а в CommandEvent команда лишь имеет тип, по которому engine (исполняющий роль «receiver») определяет, обработать ее или нет. В принципе, любой engine может начать обрабатывать и другие команды, если их добавить вручную с помощью addHandableCommand().

Горячие клавиши реализуются так же просто. Их менеджер хранит экземпляры класса Shortcut, которые содержат CommandEvent и комбинацию клавиш, которая вызывает эту команду. При нажатии на кнопки просматриваются комбинации. Если комбинация есть в массиве, выполняется соответствующее задание.


Приложение с исходниками и инструкциями лежит на гугл-коде:
code.google.com/p/easyworkloadtool

Выводы


Мы спроектировали приложение, которое полностью удовлетворяет поставленным требованиям. Оно надежно работает и обладает практически неограниченным потенциалом для расширения и дополнения новыми возможностями. Его архитектура позволяет удобно разрабатывать и поддерживать его как одиночному программисту, так и команде, так как каждый сотрудник может независимо от других работать со своим engine’ом.
Описанная архитектура может использоваться при разработке практически любого клиентского приложения. С различными модификациями она успешно применяется в ряде наших проектов
Tags:
Hubs:
Total votes 5: ↑4 and ↓1+3
Comments3

Articles

Information

Website
competentum.ru
Registered
Founded
1993
Employees
101–200 employees
Location
Россия