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


    В нашей компании для планирования оперативной загрузки сотрудников менеджеры использовали 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’ом.
    Описанная архитектура может использоваться при разработке практически любого клиентского приложения. С различными модификациями она успешно применяется в ряде наших проектов
    Competentum
    0.00
    Company
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 3

      0
      А вы не пробовали уже готовые mvc-фреймворки? Например, PureMVC или Robotlegs. или же вы целенаправленно отказались от готовых решений в пользу своей разработки?
      Вопрос по MVC схеме: почему у вас событие об обновлении не слушается вьювером? Можно ведь использовать флексовый механизм Bindable. тогда вообще отпадает необходимость вручную обновлять визуальные компоненты.
        0
        Хотелось самим спроектировать схему взаимодействия между отдельными блоками системы. Это интересно

        У нас на картинке неточность, исправлю. В основном view и его элементы обновляются через binding, это очень удобно. Но некоторые команды вызывают полное обновление view, которое контролируется engine'ом.
          0
          Очень интересный для новичка материал. Можно поучиться модульности программирования во flex / air, использованию сторонних библиотек, узнать новое об «использовании паттернов», организации последовательной обработки событий и вообще профессиональному стилю программирования. Только вот, какая бочка меда без ложки дегтя?

          Я реагирую на Вашу статью как новичок во flex / air, но имеющий некоторый опыт программирования в других языках.

          Помимо указанных плюсов, бросаются в глаза явные минусы. Вместо того чтобы писать собственно о своей программе, Вы пишите в основном об абстрактных концепциях программирования, имеющих разные названия в разных языках, но, по сути, являющиеся общим местом. У Вас это называется «схемой MVC», реализацией паттернов «Chain of responsibility», «Command», аналога «Memento». Хотя про паттерны я ничего толком у Вас не узнал, благо Интернет помог. Если Ваша личная заслуга в собственной реализации известных шаблонов программирования, то пишите об этом, а не о программе, которое практически никак не реализована, либо безжалостно урезана.

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

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

          Теперь собственно про Вашу программу. И если реализация на уровне кода сделана достаточно профессионально, то на уровне идеи – ниже всякой критики. И чему Вы гордитесь? Что отображаете профессионально две таблицы (грида), в которых можно рисовать «бревна»? Больше ничего Ваша демо делать не умеет. О чем это говорит?

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

          Ясно, что функционал программы Вы покоцали. Чтобы никто на него не позарился? Но именно этот функционал идейно очень слаб, а сильна именно формальная сторона организации программы, которой воспользоваться можно без проблем. Правда, я бы Вашей последовательной обработке событий предпочел бы параллельную, реализованную в Мэйт. Если вьюер3 или энджин3 или дайэтер3 захочет передать сообщение 2-кластеру или как Вы там его называете, то как он сможет это сделать по Вашей схеме? В Мэйт, либо в родном обработчике событий флэкс это делается без проблем.

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

          В-третьих, а с отчетами то как? Они не нужны? Или выгружаем все в эксель и печатаем там? Это профессионально?

          Знаете, подобные Вашей, базы данных реализуются в два хлопка на старой доброй 1С77. Да, там нет понтов с «перетаскиванием» мышкой, и «бревен» в гридах, но зато можно элементарно строить отчеты и легко учитывать сложнейшую схему взаимосвязей сотрудников – проектов – заказчиков – расчета стоимости – печати и т.д. и т.п. А какие у Вас преимущества, кроме реализации блажи бестолковых менеджеров? Откуда у них деньги оплачивать Ваши проекты? За счет фирмы? С такой эффективностью, зачем они вообще нужны?

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

          Only users with full accounts can post comments. Log in, please.