Часть 3: Архитектура Вида
Сен 20
Во второй части мы обсуждали состояние насыщенного интернет-приложения и рассмотрели использование паттернов Value Object и Model Locator. В третьей части наша задача сделать приложение насыщенным. Хотя идея сохранения состояния на клиенте дает большие возможности программистам, само по себе это не значит ничего, если они не визуализируют это состояние и не взаимодействуют с ним надлежащим образом.
Магазин Cairngorm должен отображать картинки продуктов, позволять пользователем выбирать их, просматривать детальную информацию, перетаскивать в свою корзину и покупать. В этой статье мы рассмотрим архитектуру вида, как предоставить пользователю насыщенный и обширный интерфейс на основе фрейворка Cairngorm. Вы познакомитесь с некоторыми хорошими способами структурирования файлов и MXML-кода, хотя сам по себе Cairngorm не требует ничего особенного от архитектуры Вида приложения.
РАЗРАБОТКА АРХИТЕКТУРЫ ВИДА
Наверно, самая главная мысль для понимания архитектуры view-логики (логики Вида) в Cairngorm приложении – это то, что Cairngorm вообще ничего не знает о ней! Одна из ключевых особенностей архитектуры Cairngorm – это то, что она не подавляет индивидуального стиля программирования.
Скорее наоборот – Cairngorm обеспечивает ясные пути взаимодействия между «насыщенным» и «приложением» в RIA.
Пользовательский интерфейс должен отображать текущее состояние приложения, показывая определенные скрины, отрисовывая модель определенными способами. Для Магазина Cairngorm это, к примеру, означает отображение продуктов в виде иконок вместо таблицы, определяя множество видимых продуктов с помощью слайдера, задающего ценовой диапазон, или определение момента, когда пользователь завершил процесс выбора и его нужно провести через процесс выписки счета. Что именно отображать, решается на основе модели, которая интегрирована с моделью. Мы уже обсудили паттерны Value Object и Model Locator в деталях, но вскоре мы завершим этот пазл.
Насыщенное Интернет-приложение это взаимодействие между пользователем и приложением. Лучший вид такого взаимодействия – двусторонний. В то время, когда пользовательский интерфейс должен «говорить» пользователю, отображая состояние модели, он должен «слушать», что пользователь хочет сделать с приложением и реагировать соответственно. В данном случае пользовательские действия называются жестами, пользователь делает запросы к приложению жестами, желая выполнить определенные действия с ним, нажимая на кнопки, перетаскивая объекты, заполняя и отправляя формы и т.п. Отвечая на пользовательские жесты, выполняя что-то по просьбе пользователя, а потом «говорит» ему в ответ, обновляя вид (или точнее сказать Модель, которая, как вы уже знаете, автоматически обновляет Вид) – это поток общения между RIA и пользователем. В этой статье я буду обсуждать паттерн Cairngorm, который может быть связующим звеном между пользователем и RIA.
Далее в этой статье я закончу обсуждение паттерна Model Locator, объясню как пользоваться связыванием данных, чтобы обновлять Вид в Магазине Cairngorm.
Потом я немного расскажу о некоторых лучших практиках структурирования кода Вида, хотя Cairngorm этого не требует. Вы можете использовать мои советы и без использования Cairngorm. Однако, если вы будете использовать Cairngorm, следование этим советам поможет вам принят архитектурные решения для MXML компонентов.
В конце я познакомлю вас с «микроархитектурой внутри микроархитектуры», концепцией, называемой «Задание для Работника». Задание для Работника это соединение паттернов, которые команда Adobe Consulting впервые использовала в Cairngorm, как способ посредничества между пользователем и приложением, исходя из функциональной декомпозиции свойств приложения.
ПРИВЯЗЫВАНИЕ ВИДА К МОДЕЛИ
На данный момент у вас есть понимание Model Locator как синглтона, который используется для хранения и получения любого состояния в нашем интернет-приложении. Более того, наше приложение хранит состояние в виде Value Object’ов (VOs) и массивов Value Object, а не простыми типами (строками, числами, булевыми), которые мало что могут сказать другим программистам.
Теперь я подробно расскажу о стратегии Model Locator и проблемы которые решает этот паттерн, и то, как вы можете использовать мощные возможности Flex Data Binding для доступа к Model Locator и Value Object.
Панель Product Details в Магазине Cairngorm показывает детальную информацию о продукте, на котором кликнул пользователь либо на табличном виде либо на иконке.

Рис. 1. Детальный просмотр продукта
Точка входа в Магазин Cairngorm это файл Main.mxml, который делит интерфейс на две части:
- BodyPanel: панель для отображения главной части приложения, отображает табличный и графический вид списка продуктов, а также процесс выписки счета;
- SideArea: прямоугольная область, содержащая компоненты ProductDetails и ShoppingCart.
Посмотрите на объявление компоненты SideArea. Также как во второй части, здесь используется связывание данных для отображения selectedItem из ModelLOcator в компоненте ProductDetails, который является объектом класса ProductVO:
<details:ProductDetails
id="productDetailsComp"
width="100%" height="325"
currencyFormatter="{ ModelLocator.currencyFormatter }"
selectedItem="{ ModelLocator.selectedItem }"
addProduct="addProductToShoppingCart( event )" />
Откройте файл ProductDetails.mxml, чтобы увидеть реализацию этой компоненте. Заметьте, что благоразумно использовать собственную ссылку на объект selectedItem:
<mx:Script>
<![CDATA[[
// set during MXML instantiation
public var selectedItem : ProductVO;
]]>
</mx:Script>
<mx:HBox>
<mx:Canvas
width="150" height="140"
clipContent="false">
<mx:Image
id="image"
source="{ selectedItem.image }"
mouseDown="beginDrag();"
mouseOverEffect="big" mouseOutEffect="small" />
</mx:Canvas>
<mx:VBox
width="100%" height="100%"
styleName="productDetailsTitle">
<mx:Label
id="name"
text="{ selectedItem.name }"
styleName="title" />
<mx:Label
id="price"
text="{ selectedItem.price}"
styleName="price" />
</mx:VBox>
</mx:HBox>
<mx:Text
id="description"
width="100%" height="100%"
text="{ selectedItem.description }"/>
</mx:VBox>
При использовании связывания данных, любые изменения объекта selctedItem в классе ModelLocator вызывают срабатывание биндинга в инстанции <details:ProductDetails />, обновляя значения атрибута selectedItem компонеты ProductDetails. Это обновление приводит к срабатыванию всех биндингов в selectedItem класса ProductDetails, обновляя картинку, имя, описание и цену нового выбранного продукта.
Это главный принцип работы механизма Flex Data Binding. Cairngorm использует это для гарантирования того, что новые данные пришли из ModelLocator, а не откуда-то еще.
Заключительный элемент пазла – как обновить ссылку selectedItem в ModelLocator. В компоненте ProductsAndCheckoutViewStack есть две инстанции:
<productview:GraphicalProductList
id="graphicalProductList"
products="{ ModelLocator.products }"
selectedItem="{ ModelLocator.selectedItem }"
select="ModelLocator.selectedItem = event.target.selectedItem;" />
<productview:TextualProductList
id="textualProductList"
products="{ ModelLocator.products }"
selectedItem="{ ModelLocator.selectedItem }"
select="ModelLocator.selectedItem = event.target.selectedItem"
currencyFormatter="{ ModelLocator.currencyFormatter }" />
Обе компоненты отвечают за отображение в графическом и текстовом виде каталога продуктов публикуя события выбора продукта, когда пользователь кликает на новом продукте. Обрабатывая эти события, просто сохраняем значение ссылки на ProductVO (элемент списка, на котом только кликну пользователь) в selectedItem в ModelLocator.
Поскольку Модель сама уведомляет Вид о произошедших изменениях с помощью связывания данных (data-binding), программисту не нужно самостоятельно заботится о том, что именно и как уведомить об изменениях в Модели.
Это иллюстрирует идею «одна модель – множество видов». Мы имеем две инстанции компонентов GraphicalProductList и TextualProductList. Обе имеют атрибут products и обе привязывают это значение к одному и тому же свойству в модели: ModelLocator.products.
Атрибут ModelLocator.products это массив объектов ProductVO, как это было отмечено во второй части.
В компоненте GraphicalProductList этот массив продуктов является dataProvider’ом для TileList, у которого задан itemRenderer (ProductThumbnail), способный отображать значения ProductVO:
<mx:TileList
id="tileListComp"
width="100%" height="100%"
dataProvider="{ products }"
cellRenderer="org.nevis.cairngorm.samples.store.view.productview.ProductThumbnail"
itemWidth="120" itemHeight="116"
dragEnabled="true"
change="updateSelectedProduct( event );"
borderStyle="none" />
В тоже время, TextualProductList тоже не делает ничего особенного, кроме как передает массив ProductVO в dataProvider для DataGrid, который отображает надлежащим образом объекты ProductVO:
<mx:DataGrid
id="dataGridComp"
dataProvider="{ products }"
change="updateSelectedProduct( event );"
dragEnabled="true"
width="100%" height="100%">
Этот пример прекрасно демонстрирует стратегию Model Locator в действии. Каждый компонент объявляет и отрисовывает свою локальную модель, так что он и понятия не имеет, что работает внутри архитектуры Cairngorm. Только на самом верхнем уровне использования компоненты – обычно это MXML файл – вы привязываете его локальную модель к модели хранящейся в ModelLocator.
ЛУЧШИЕ МЕТОДИКИ ПРИ РАБОТЕ С MXML
Только что мы коснулись вопроса правильного подхода к программированию с использованием MXML, который рекомендует команда Adobe Consulting для разработки пользовательского интерфейса.
Один ключевой подход к разработке – «разработка с компонентами на уме». MXML позволяет невероятно легко создавать компоненты, расширяя базовый класс для специфичной задачи конкретного приложения. Если вам когда-нибудь приходилось разбираться в чужом MXML коде, чтобы проследить необходимую цепочку, пробираясь через иерархию VBox, содержащий HBox, содержащий TileList, содержащий VBox и т.д., вы быстро поймете значение создание компонент, которые имеют семантическое значение.
Если вы посмотрите код Магазина Cairngorm, вам будет легко разобраться в MXML компонентах в связи с их визуальным представлением на экране. Создавая компоненты как Main, SideArea, ProductDetails, ShoppingCart, ProductDetails, ProductThumbnail, GraphicalProductList, TextualProductList, ProductAndCheckoutViewStack и т.п., вам будет легко проходить по коду не только своему но и чужому.
Используя компонентный подход к проектированию приложения, научитесь использовать событийную модель Flex, чтобы не связывать свои компоненты жестко. К примеру, создавайте пользовательские (кастомные) события, чтобы описывать то, что произошло в программе на пользовательском уровне, а не на уровне Flex фреймворка. Ощутите разницу между кнопкой в VBox’е с неким обработчиком на событии click="" и компонентой ShoppingCart, публикующей событие productAdded. Создавая свои события, публикуйте их от своей компоненты и обрабатывайте их соответственно. Тогда ваш код будет гораздо легче в поддержке.
Посмотрите еще раз на компоненту ProductDetails, которая кастомизирует под наши задачи флексовую Panel и которая публикует событие когда пользователь нажимает на кнопку «Add to Cart»:
<mx:Panel
xmlns:mx="http://www.macromedia.com/2003/mxml"
xmlns:details="org.nevis.cairngorm.samples.store.view.productdetails.*"
title="Product Details"
styleName="productDetails">
<mx:Metadata>
[Event("addProduct")]
</mx:Metadata>
<mx:Script>
<![CDATA[
private function addProduct() : Void
{
var event : Object = new Object();
event.type = "addProduct";
event.product = selectedItem;
event.quantity = quantity;
dispatchEvent( event );
}
: : :
<mx:Button
label="Add to Cart"
click="addProduct();" />
</mx:Panel>
Здесь событие нажатия на кнопке превращается в событие addProduct, которое публикует наша компонента. Более того, это событие имеет свойство selectedItem (т.е. сам продукт который нужно добавить) и quantity – то количество продуктов, которое должно быть добавлено в корзину покупателя.
Далее, когда мы используем компоненту ProductDetails (в SideArea.mxml), мы обрабатываем это событие гораздо более понятные образом:
<details:ProductDetails
id="productDetailsComp"
width="100%" height="325"
currencyFormatter="{ ModelLocator.currencyFormatter }"
selectedItem="{ ModelLocator.selectedItem }"
addProduct="addProductToShoppingCart( event )" />
И хотя эти рекомендации не имеют никакого отношения к разработке приложения на Cairngorm, следование им улучшит ясность вашего кода и облегчит распознавание точек взаимодействия между MXML и ActionScript вашей программы, как в Виде так и подлежащей бизнес логике, нанизанной на Cairngorm.
ЧТО ДАЛЬШЕ
Эта статья повествовала о том как создавать Вид, используя строительные блоки из MXML и ActionScript. И что более важно, я рассказал как строить Вид на основе архитектуры Cairngorm.
На данный момент вы уже знаете как управлять состоянием клиента, используя паттерны Model Locator и Value Object. Также я дал некоторые рекомендации и лучшие техники на счет того, каким образом организовывать структуру Вида, то есть MXML и ActionScript классы, несмотря на то что Cairngorm не накладывает никаких ограничений и не выдвигает никаких требований к его архитектуре.
Мы рассмотрели как описать Вид и как сохранить его состояние. В четвертой части мы рассмотрим как расширить функционал нашего приложения, добавив бизнес логики в командах – то что является «приложением» (application) в RIA.