Пример использования Mate Flex Framework

Posted by - March 24, 2009

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

Некоторые коллеги постоянно норовят разузнать все детали и нюансы использования Mate на практике и в связи с этим я решил описать пример типового архитектурного решения основанного на модели реального приложения. Он похож на примеры с сайта фреймворка, но расписан пошагово с конкретными рекомендациями на всех уровнях.

Итак, я подготовил проект opportunities (так сказать, подающий большие надежды) на Google Code с доступными исходниками. Для начала рассмотрим его он-лайн:

У нас есть список сущностей с возможностью редактирования каждой из них в отдельной форме, а также добавлять новые — проще не бывает. Следующее решение, как всегда покажется необоснованно сложным для подобного примера, но незабываем, что перед нами стоит задача ознакомления с Mate, а не написания примера в 25 строк экшен-скрипта.

Я использую, так называемую схему «двустороннего коммуницирования посредством модели и инжектинга», на сайте фреймворка есть отличная диаграма как этого так и других приёмов:

Рассмотрим каждый элемент диаграмы основываясь на классах моего примера. Пункт 3 исключаем, так как для доступа к серверу мы используем специальный класс сервиса OpportunitiesMockService наследуемый от ServiceBase (я уже описывал этот приём ранее).

Model

Я определил три класса модели. Самый элементарный, зависимый от представления, класс элемента списка OpportunityVO. Второй класс OpportunitiesDataProvider, является списком экземпляров OpportunityVO, он инкапсулирует в себе вызовы серверных методов и для удобства наследуется от ArrayCollection. Это даёт возможность использовать его провайдером данных для большинства списочных компонентов из Flex фреймворка.

Третий — OpportunityDataProvider, предназначен для работы с конкретным редактируемым OpportunityVO из списка. У него есть публичное свойство opportunity, а также методы для манипуляций с ним. Этот класс диспатчит события opportunityCreated и opportunitySaved типа DataProviderEvent, которые информируют о завершении обработки opportunity (так как эти действия зачастую связаны с вызовом серверных методов, мы неможем ожидать их синхронного выполнения в потоке вызова методов класса). Реализация класса очень простая, но в реальности, в него нужно помещать все трансформации, инициализации и прочие процедуры над элементом, которые не связаны с логикой работы представления, и вполне успешно могут быть отделены от него.

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

View

Для представления также определены три класса, первый OpportunitiesListView для показа списка элементов. У него есть одно публичное свойство dataProvider, а также, он диспатчит два события createOpportunity и editOpportunity типа OpportunityEvent, которые говорят сами за себя.

Второй класс OpportunityEditView определяется свойством opportunity, в которое нужно присваивать непосредственно объект редактирования OpportunityVO, а также он диспатчит другие два события saveOpportunity и closeOpportunity также типа OpportunityEvent.

Третий — Index, является композицией предыдущих. В нём доступно свойство selectedIndex, которое определяет текущую видимую форму.

Это наше представление. Как видим, его классы наследуются от стандартных контейнеров Flex фрейворка, они не имеют зависимостей от провайдеров данных модели, являются пассивными и выглядят как довольно самодостаточные компоненты для повторного использования — то, что нам и нужно.

Mate Event Maps

Перейдём непосредственно к роли Mate в связывании модели и представления. Все декларации этого фреймворка описываются в так называемых «картах событий». Это MXML файлы, которые поддерживают биндинг и все остальные удобства Flex Builder-а. По мере увеличения проекта, эти карты довольно сильно разростаются, так что для разных групп коммуникаций я завожу отдельные файлы. В данном примере их четыре.

Одна из карт — DataInjectionMap, описывает связи между представлением и моделью, выполняется только один раз при запуске приложения. Это частичная реализация паттерна IoC (Inversion of Control), которая работает необычным образом: при необходимости, она создаёт только экземпляры классов модели и слушая события родительского Application, в момент создания представлений, выполняет описанные связывания. Типичная нотация с использованием тегов Injectors и PropertyInjector:

<Injectors
	target="{OpportunitiesListView}">
 	<PropertyInjector
		targetKey="dataProvider"
		source="{OpportunitiesDataProvider}" />
</Injectors>

Здесь мы задаем связь на основе биндинга между экземплярами классов OpportunitiesListView и OpportunitiesDataProvider посредством свойства dataProvider, данные которого в результате попадут в DataGrid.

Остальные: OpportunitiesEventMap, DataProviderEventMap, NavigationEventMap, непосредственно описывают действия, которые должны происходить при попадении в поле видимости Mate (Event Bus) каких-либо событий как из представления так и из классов модели. Например, нотация с использованием тегов EventHandlers, MethodInvoker и EventAnnouncer:

 <EventHandlers
	type="{OpportunityEvent.EDIT_OPPORTUNITY}">
 	<MethodInvoker
		generator="{OpportunityDataProvider}"
		method="editOpportunity"
		arguments="{[event.data]}"/>
 	<EventAnnouncer
		type="{NavigationEvent.NAVIGATE_EDIT_VIEW}"
		generator="{NavigationEvent}"/>
</EventHandlers>

Это выражение читается так: в тот момент когда представление продиспатчит событие editOpportunity (по-сути нажатие кнопки Edit), у экземпляра класса модели OpportunityDataProvider должна запуститься операция редактирования экземпляра класса OpportunityVO, которые доступен из события.

Так же, посылается специальное событие NavigationEvent, в результате которого представление должно переключиться в режим редактирования. Внутри класса OpportunityDataProvider, должен произойти вызов серверного метода для получение экземпляра OpportunityVO с последним состоянием элемента (но в моём примере, я упростил схему, и сохраняю в свойстве opportunity модели, тот же объект). Далее, так как мы указали связывание (injection) в DataInjectionMap между OpportunityEditView и OpportunityDataProvider по свойству opportunity, редактируемый элемент будет доступен в представлении.

В свойствах generator или source вышеописанных тегов, мы указываем класс модели, не экземпляр этого класса. Mate автоматически создаст экземпляр в нужный момент, причём, по-умолчанию, только один раз. Фреймворк имеет внутренний кеш для всевозможных экземпляров модели или событий, и ищет там необходимые объекты для повторного использования. Эта схема похожа на паттерн Singleton, но она гораздо практичнее, так как нет лишнего связывания между сущностями системы, которые неизбежны при использовании «одиночек» (кстати, довольно сильный недостаток прочих фреймворков типа Cairngorm или PureMVC).

И напоследок, рассмотрим специфику карты NavigationEventMap, в которой описаны ответные действия на события NavigationEvent. В общем-то, переключение режимов представления могло бы быть реализовано непосредственно в нём. Но, как показала практика, в больших проектах, визуально легче контролировать подход внешнего управления представлением. Например, если необходимо подключить deep-linking (поддержку изменения адресной строки броузера), мы введём ещё один тип событий BrowserNavigationEvent, которые будут использовать уже готовые списки действий в картах Mate. Итак, нотация:

<EventHandlers
	type="{NavigationEvent.NAVIGATE_EDIT_VIEW}">
 	<MethodInvoker
		generator="{OpportunityViewState}"
		method="setSelectedIndex"
		arguments="{[1]}" />
</EventHandlers>

При попадении в Mate события navigateEditView, вызывается метод у вспомогательного класса модели OpportunityViewState, который является проксирующим. Я не хотел бы вводить для этих целей этот класс, а бы присваивал значения в selectedIndex прямо в классе представления Index, но в свойство generator тега MethodInvoker (как и всех других тегов), нельзя указывать классы визуальных компонентов (то-есть можно, но это будет неправильно, так как фреймворк создаст экземпляр, недоступный никому). Суть в том, что, как я уже писал выше, Mate не создаёт экземпляры визуальных компонентов(и не пытается, это делает MXML), он слушает события всего приложения, и когда компонент попадёт в display list флеш плеера, фреймворк начинает с ним «сотрудничать» (зачастую для определения (injection) зависимостей). Получается, что есть внутренний кеш всех созданных классов самим фреймворком и отдельный список компонентов, которыми заведует сам флеш плеер и они не пересекаются между собой.

Вот и всё. Информацию о всех доступных тегах фреймворка можна найти в документации на сайте Mate. Надеюсь описание не получилось чересчур запутанным. С удовольствием отвечу на все адекватные вопросы по теме.

P.S. Кстати, на днях вышло обновление Mate до версии 0.8.7, в котором появилась возможность задавать связи (injection) между интерфейсами.

11 Comments on Пример использования Mate Flex Framework

Closed

  1. ez says:

    Довольно интересно. Надо будет попробовать применить на новых проектах.

  2. Racer says:

    Тяжеловато написано. Для тех, кто более-менее в курсе, наверное прокатит, но для ознакомления – мудрёно.
    Всё равно, спасибо :)

  3. flibustier says:

    Добрый день.
    Скажите, пожалуйста, адекватно ли использовать mate для переноса на flex Чата Вконтакте?
    http://vkontakte.ru/source/Chat.zip – сам он написан в среде flash.

    • tearaway_Tea says:

      Я быстро просмотрел исходники — написано аккуратно и вобщем-то правильно. Разделена модель и представление и в такой проект можно было бы включить MATE. Но, делать этого не нужно. Во-первых, использование MATE предполагает использование MXML, который является неотъемлемой частью Flex проектов, а не написанных на чистом Action Script как в вашем примере.

      Если вы готовы утяжелить ваш чат лишними 200-300 Кб (я бы не делал), то вперёд: перепишите UI с использованием Flex контролов и свяжите их с моделью с помощью MATE.

      Но, не делайте этого =). Не того уровня приложение.

      • flibustier says:

        Сразу забыл оговориться – чат не мой, а, по сути, пример приложения для вКонтакте.
        Я бы хотел использовать этот пример для своего приложения, а я его пишу с использованием flex и mxml.

        > “перепишите UI с использованием Flex контролов и свяжите их с моделью с помощью MATE.”
        Спасибо, хотел убедиться в том, что в этом есть смысл и не mate не будет избыточным в этом приложении.

  4. Нарешті прочитав цей пост.
    Дуже зрозуміло написано, як на мене.
    З твоєю допомогою я здається освоїв основні речі в МАТЕ і вроді їх вдається гарно застосовувати.
    От лише я думаю, що потрібно поменше Event Announcer-ів застосовувати, бо вони підвищують асинхронність системи в цілому.
    Теги які я зараз застосовую і які мабуть складають основу: EventHandler, MethodEnvoker, Injectors, ObjectBuilder, DataCopier, Listener.
    А ще дивно що в документації на МАТЕ сайті немає нічого про ListenerInjector, такщо спасибі що розказав нам :)

    • tearaway_Tea says:

      Хм, я так і не знайшов адекватного застосування ObjectBuilder і DataCopier-у, цікаво, як це вийшло у вас. Потрібно глянуть свн. А про ListenerInjector писали в новинах, коли вийшла якась нова версія фреймворка. Вони, тіпа, не встигають оновлювати документацію.

      • Приклад застосування ObjectBuilder-а:
        деколи виникає необхідність продіспатчити подію з модельки, але так щоб її зловило МАТЕ. Гарний спосіб для цього при конструюванні модельки передати в її конструктор dispatcher: GlobalDispatcher, запамятати собі його там і потім по потребі використовувати:

        <objectbuilder generator="{SalesReportsModel}"
        	constructorArguments="{[scope.dispatcher]}" registerTarget="true"/>

        Це звичайно можна було б зробити і іншими способами, але цей мені подобається.

        • tearaway_Tea says:

          Ну не знаю, на скільки це гарний спосіб. Хіба що, якщо ти передаєш dispatcher як IEventDispatcher. А якщо ти передаєш типизований GlobalDispatcher, то це фігня. Краще вже писати так:

          new Dispatcher().dispatchEvent(new Event("someEvent"));

          Взагалі, краще, щоб у моделі конструктор був без параметрів, тому що десеріалізатор AMF нормально її не створить.

  5. scurresee says:

    Спасибо, это то что надо, но хотелось бы больше