Search

Rss Posts

Rss Comments

Login

 

Часть 3: Архитектура Вида

Сен 20

Автор: Steven Webster

Во второй части мы обсуждали состояние насыщенного интернет-приложения и рассмотрели использование паттернов 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. Детальный просмотр продукта
Рис. 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.

Часть 2: Сохранение состояния на стороне клиента

Сен 20

Автор: Steven Webster

В части 1 этой публикации, я познакомил вас с Cairngorm – компактной программной архитектурой, которая упрощает решение многих сложных повторяющихся задач в процессе создания насыщенных Интернет приложений, которые называют RIA.
В этой части я опишу задачи, которые возникают в процессе разработки RIA, а в частности как хранить состояние на стороне клиента. Я поясню это на примере создания интренет-магазина, который мы будем называть "Магазин Cairngorm". Вы изучите два фундаментальных паттерна архитектуры Cairngorm: паттерн Value Object и паттерн Model Locator. Под конец этой статьи вам будет понятно, насколько прозрачным можно сделать Flex-приложение, используя эти два паттерна.

ВВЕДЕНИЕ В "МАГАЗИН CAIRNGORM"

Flex Интернет-магазин демонстрирует возможности Flex-фреймворка. Здесь хорошо видно как можно использовать различные контейнеры для разметки, навигационные контейнеры, элементы управления, эффекты, связывание данных, менеджер Drag-and-Drop, валидатор форм, и менеджер истории. Кроме того, Flex магазин следует компонентной модели построения приложения во Flex, показывая каким образом можно создать ряд независимых компонентов и связать их воедино в событийной архитектуре. Flex магазин – замечательный способ познакомиться с декларативной разметкой используя MXML и бизнес логикой реализованной на ActionScript.

Рис. 1. Магазин Cairngorm
Рис. 1. Магазин Cairngorm

Я выбрал хорошо всем знакомую идею Интернет-магазина и реализовал ее как RIA уровня предприятия – приложение которое производительно, масштабируемо, и технически сопровождаемо – с помощью Cairngorm.

Магазин Cairngorm достаточно сложное Интернет приложение, которое выиграет от реализации на микроархитектуре Cairngorm. В части 3 я покажу этот момент истины – как легко вы можете добавить, в это Cairngorm приложение, функциональности и насколько предсказуемо будет его поведение после этого, а также насколько меньше всяких рисков по сравнению с простым приложением.

МАГАЗИН CAIRNGORM: ЧЕТЫРЕ ПРОБЛЕМЫ

Вместо академических рассуждений на тему паттернов проектирования, которые мы заложили в Cairngorm, я объясню самые важные проблемы, возникающие во время разработки RIA, и как можно решить их с помощью Cairngorm. Описывая сам процесс разработки, я попутно буду объяснять паттерны, из которых состоит сам Cairngorm.
Хороший технический архитектор сначала видит разработку приложения как решение бизнес проблемы, потом как систему, которая реализует это решение, потом как техническую архитектуру в этой системе, и уже в конце, как набор классов той архитектуры.
Такой метод разработки, каким предоставляет его Cairngorm, обеспечивает большую ясность реализации, нежели просто нырять в код.
На самом высоком уровне абстракции, есть всего четыре проблемы, с которыми сталкивалась группа Adobe Consulting, толи, создавая, ипотечные калькуляторы, системы пенсионных платежей, Интернет-банкинга, электронных платежей состоящих из одной странницы, полновесных систем электронной коммерции, или же интерактивных карт. Эти проблемы это:
- сохранение состояния на стороне клиента
- разработка представления данных
- реализация характерных функций
- вызов серверных служб
Для Магазина Cairngorm предъявляются те же требования и возникают те же проблемы.
Клиентская часть приложения показывает товар покупателю, корзина покупателя хранит все приобретенные товары, и пользователь должен пройти несколько стадий во время процесса оплаты. На протяжении всего это процесса, ваше приложении должно сохранять состояние на стороне клиента.
Отдельно взятое окно такого приложения имеет много разных элементов. Оно содержит и графический материал, и список свойств товара, и детальное описание; в корзину покупателя можно добавить разные товары; и серию форм, которые должен заполнить пользователь для оформления заказа. Все это касается разработки представления данных – View.
Также есть перечень особенностей такого рода приложения. Клиент должен иметь возможность выполнить следующее:
- просмотреть все продукты и получить детальное описание выбранного
- добавить или удалить из корзины продукт
- купить продукт в своей корзине
- пройти процесс оформления заказа
Эти свойства должны быть реализованными вами.
И последнее это то, что все необходимые данные ваше приложение получает из базы данных. И более того, при оформлении заказа, может понадобиться сохранения заказа в базе данных или же отправить их, скажем, в SAP. Таким образом, ваше приложение должно быть интегрировано с серверной частью.
Как вы можете видеть, наш Магазин Cairngorm – несмотря на свою уникальность в сфере бизнеса, работой с продукцией, поведением и свойствами – выглядит точно таким же, как и любое другое RIA, если мы рассматриваем его через призму четырех вышеозначенных проблем разработки.
В этой главе я буду касаться только первой из четырех проблем: сохранение состояния на стороне клиента.

ХРАНЕНИЕ СОСТОЯНИЯ НА СТОРОНЕ КЛИЕНТА

Главное отличие RIA от традиционных вэб-приложений это то, что клиент хранит свое состоние.
Разработчикам вэб-приложений очень хорошо знакома идея запрос-ответ – серфинга без сохранения состояния. Поэтому им приходится изобретать различные приемы для поддержки состояния клиента. Либо вы постоянно передаете одни и те же данные в HTTP запросе в процессе смены страниц, или вы используете URL-кодирование, или Куки чтоб сохранить небольшое количество данных на стороне клиента, или вы используете абстракции вроде J2EE сессий – все это механизмы сохранения состояния на стороне сервера и делание их доступными для клиента.
Однако с RIA разработчики освобождаются от кандалов HTTP запрос-ответ модели. Больше не надо использовать какие либо механизмы для сохранения состояния клиента – теперь он изначально сам хранит свое состояние. Если вы когда-либо создавали десктоп приложения или RIA, скажем, с помощью Swing или AWT, то все вышесказанное покажется вам известным – так оно и есть.

РАЗНИЦА МЕЖДУ СОСТОЯНИЕМ И МОДЕЛЬЮ В MVC

Многие разработчики знакомы с концепцией MVC (Модель-Вид-Контроллер) и, пожалуй, удивляются, при чем здесь Состояние. Все очень просто: состояние это и есть модель. Но на сколько это очевидно, это также порождает ряд других проблем с описанием модели в RIA.
Самая первая из них это то, что очень сложно расчленить состояние вашего приложения на строки, числа, булевы, и на прочие примитивные объекты. Здесь я не хочу вдаваться в обсуждение лучших практик объектно-ориентированного программирования. Хотя бы потому, что для понимания данного материала вы уже должны их знать.
Тем не менее, я решительно утверждаю, что те данные, которые содержит клиент – есть Состояние или Модель, или любой другой термин, имеющий семантическое значение. Что значит "семантическое значение" в данном контексте? Ну, например, если вы продаете рыболовные приманки, то ваша объектная модель будет включать «муха», «мотыль», «блесна». Если вы продаете ипотечные кредиты, то ваша объектная модель будет включать «продукт», «процентная ставка», «рефинансирование», «условия». Если вы продаете географические карты, то ваша объектная модель будет включать «маршрут», «координаты», «достопримечательности», «заправки».

СИНХРОНИЗАЦИЯ СОСТОЯНИЙ КЛИЕНТА И СЕРВЕРА

Есть одна проблема, связанная с RIA, уникальная для клиента хранящего свое состояние: состояние клиента обычно отображает состояние на другом конце линии – сервера. Давайте немного детальней поговорим об этом.
При разработке серверного приложения, одним из ключевых моментов есть то, что состояние хранится между двумя уровнями программной модели: между бизнес-уровнем и уровнем интеграции.
Уровень интеграции обычно включает в себя базы данных, мэйнфреймы, очереди сообщений, системы ERP (система управления предприятием – прим. перев.), CRM системы (системы управления продажами – прим. перев.) и другие информационные системы предприятия, которые хранят данные и управляют процессами характерными для конкретной проблемной области.
Все эти системы, при разработке серверного ПО, собираются воедино и предоставляются для доступа пользователю через промежуточный уровень, который называют «бизнес-уровень». Есть целое множество решений, призванных решить проблемы хранения состояния между двумя этими уровнями. Прежде всего, это из-за того, что представление данных на двух этих уровнях, может кардинально отличаться. Для примера, понятия сущности, отношения, видов, запросов, которые характерны для баз данных, должны быть отображены из модели базы данных на объектные модели данных приложений бизнес-уровня. Для этих задач могут использоваться такие технологии как Enterprise JavaBeans, Hibernate (8), ADO.NET.
Независимо от выбранного решения, разработчик несет большую ответственность за обеспечение целостности данных при передаче их между этими уровнями. Как только вы начинаете хранить одни и те же данные в двух разных местах, появляется опасность изменения данных в одном месте, которые не отобразились в другом, или же одни данные перезаписываются другими. Также разработчик должен заботится о параллельных процессах, блокировках, транзакциях, откатах и других средствах, гарантирующих то, что модель данных целостная в разных участках архитектуры.
Поскольку вы RIA разработчик, вы абстрагированы от сложностей взаимоотношений бизнес уровня и уровня интеграции. Вас больше беспокоит то, как обеспечить целостность данных в бизнес и презентационном уровнях.
Самое простое для вас в этом случае, это согласовать модель данных на клиенте и на сервере, это уже сократит ваши усилия для синхронизации данных.

ВВЕДЕНИЕ В ПАТТЕРНЫ VALUE OBJECT И DATA TRANSFER OBJECT

Всякий раз, когда вам нужно представить значение какой-то сущности в приложении, вы создадите класс, представляющий эту сущность. Например, в Магазине Cairngorm есть сущность представляющая продукт. Каждый продукт имеет множество атрибутов: имя, описание, цена, картинка, иконка.
Этот объект-значение (value object) играет двойную роль в RIA приложении. Кроме того, что он хранит значение на клиенте, он также может быть полезной структурой с помощью, которой вы можете обмениваться данными между различными уровнями приложения. Например, возможна такая ситуация, когда SQL-запрос к базе данных возвращает некий результат, который конвертируется в массив подобных объектов-значений.
Передавая такие объекты-значения между различными уровнями приложения, вы тем самым изолируете каждый уровень от внутренней логики работы его соседних уровней. Таким образом, промежуточному программному слою не будет нужды волноваться, где именно был создан полученный объект-значение: из SQL-запроса, хранимой процедуры или вызова вэб-службы. Повторно используя такие объекты-значения, вы расчленяете архитектуру приложения, передавая данные между уровнями как объекты со значением проблемной области («Продукт», «Рыба», «Карта»), нежели их техническими значениями («Запись», «Результат», «Группа данных»). Все это привело к именованию этого паттерна как Value Object (объект-значение), вместо Data Transfer Object (DTO, объект передачи данных).
Мы придумали Cairngorm еще до того, как термин DTO широко распространился, поэтому я называю бизнес сущности Value Object или просто VO.
Для примера, рассмотрите VO из Магазина Cairngorm (он располагается в org/nevis/cairngorm/samples/store/vo):

class org.nevis.cairngorm.samples.store.vo.ProductVO implements ValueObject, Comparable
{
	public static var registered:Boolean = Object.registerClass( "org.nevis.cairngorm.samples.store.vo.ProductVO", ProductVO );

	public var id : Number;
	public var name : String;
	public var description : String;
	public var price : Number;
	public var image : String;
	public var thumbnail : String;
}

Конечно же, это не космические технологии. ProductVO есть не что иное, как набор атрибутов, которые описывают класс Product как продукт. Всякий раз, когда вы будете сохранять состояние на клиенте о продуктах, храните их в экземплярах класса ProductVO. Если приложение должно знать какой сейчас «продукт выбран», сохраняйте эту информацию в экземпляре ProductVO. Если приложение должно хранить список продуктов в корзине покупателя, сохраняйте эту информацию на клиенте в массиве экземпляров ProductVO.
Более того, если вы получаете от сервера список продуктов, или передаете продукты назад на сервер, передавайте их как объекты-значения. Кто-то может сказать, что здесь мы используем DTO. Это есть одно и тоже – помните это.

СОХРАНЕНИЕ ЦЕЛОСТНОСТИ МОДЕЛИ ДАННЫХ МЕЖДУ FLEX И JAVA

Серверное ПО, обычно представляет собой хорошо разработанную объектную модель. При использовании J2EE, скорее всего на сервере уже имеется Java модуль (package) объекта-значения, представляющий собой именно тот объект, который нужен клиенту.
В этом случае, сервер проделывает для Flex-приложения большую работу. Если вы хотите передать ActionScript объект-значение для Java приложения, Flex может автоматически создать эквивалент Java-объекта. Аналогично, когда Java приложения возвращает объект-значение клиенту, Flex конвертирует его в эквивалент объекта ActionScript.
И даже более, если объект не просто один объект, а составной граф из объектов, Flex выполнит всю работу по преобразованию в нужный объект, и даже сохранит связи между объектами. Таким образом, когда у вас есть объект ShopingCartVO, содержащий список ProductVO, а каждый ProductVO содержит список объектов AddressVO (адрес доставки и адрес для счета-факутры), и возможно объект DiscountVO, Flex обеспечит целостность объектов при передаче между RIA приложением и серверным ПО.
Для того чтобы обеспечить отображение (мапинг) серверных и клиентских объектов, нужно дать некую подсказку для Flex о существовании связи между объектами. В Cairngorm для этого нужно указать в каждом экземпляре объекта-значения, следующее:

public static var registered:Boolean = Object.registerClass( "org.nevis.cairngorm.samples.store.vo.ProductVO", ProductVO );

Это гарантирует то, что класс ProductVO на клиенте отображается на Java класс ProductVO.java, который находится в пакете org.nevis.cairngorm.samples.store.vo.ProductVO на сервере.

Использование XDoclet2 для ActionScript

XDoclet2 – это генератор кода ActionScript, написанный на Java. Джо Берковец из Allurent создал это проект с открытым исходным кодом, который создает ActionScript классы для VO из Java-аналогов. Вы можете больше узнать об этом проекте, а также скачать исходный код из SourceForge по адресу www.allurent.com/joeb/xdoclet2.

Резюмируя, Value Object обеспечивает:
- Использование лучшего подхода к программированию, представляя данные на клиенте в понятных терминах для данной предметной области
- Поддержка целостной модели данных между клиентом и сервером
- Использование возможностей Flex для транслирования объектов-значений между Java и ActionScript

Но получение данных клиентом от сервера, это только часть проблемы. А что вы будете делать с клиентскими данными и где вы будете их хранить, чтобы решить проблему полностью?

СВЯЗЫВАНИЕ МОДЕЛИ И ВИДА ВОЕДИНО

Смысл данных на презентационном уровне в n-уровневом приложении – это их отображение. А смысл R в слове RIA – это отображение этих данных насыщенным и разнообразным способом.
Связывание данных во Flex’e, одно из скромно умалчиваемых его особенностей. Связывание данных позволяет взаимодействовать двум объектам как источник и назначение. Изменение в объекте-источнике, сразу отображается в объекте-приемнике. Если объектом-приемником является визуальный компонент, а объектом-источником данные клиентского приложения, вы получаете мощное средство отображения состояния клиентского приложения пользователю в масштабе реального времени. Таким образом, вид клиентского приложения всегда останется актуальным, модель (состояние клиента) будет оповещать вид (пользовательский интерфейс), если вы свяжете модель и вид вместе.
В Adobe Consulting мы наблюдали за командами разработчиков процессом, создающими решения для обновления пользовательского интерфейса, когда подлежащая модель изменялась. Более того, эти разработчики легко попадали в путаницу, если эти данные отображались в разных местах и разным способом. Скажем, у вас есть последовательность чисел, которая была обновлена новыми данными из сервера. В зависимости от места в приложении, их надо показать либо в таблице или графиком, или в каком-то резюмированном виде согласно бизнес логике. Но если в результате рефакторинга, вы вынесете эти данные в модель, и используете механизм связывания, вы устраните много сложностей для процесса взаимодействия в RIA.
Более того, мы заметили, что каждый разработчик хранит нужные ему данные для своей части программы, в своем участке кода. Соответственно, если эти данные нужны другому разработчику в другой части приложения, ему придется заботиться, чтоб эти данные теперь отразились еще и в другом месте.
Часто мы видели, что программисты обращается к данным через длинную цепочку ссылок на компоненту, например: mx.core.Application.application.storeView.textualList.productGrid. Такой подход является невероятно хрупким – добавление или удаление компонента (storeView, textualList) потребует от разработчика обновления ссылок на productGrid.
Еще одним вариантом может быть передача данных от одного MXML-компонента к другому. Если рассматривать MXML компоненты как деревья, а данные будут их листьями, то некоторые листья будут нужны на разных деревьях. Единственное решение здесь, это найти общую точку ответвления для этих листьев, чтобы данные можно было передавать в виде:

<myView:MyComponent data="{this.data}" />

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

ВВЕДЕНИЕ В ПАТТЕРН MODEL LOCATOR

Видя, как разработчики постоянно попадают в эту ловушку, команда Adobe Consulting осознала, что паттерн Model Locator является правильным решением для Flex программистов. Паттерн Model Locator уникален, поскольку он не заимствован из каталога паттернов J2EE. Мы создали его специально для Flex приложений. Нашей целью было создать единственное место, где будет хранится состояние Flex приложения и где View-компоненты должны находить данные, которые им нужно отобразить.
Наш паттерн Model Locator предполагает использование связывание данных (data binding), так что визуальные компоненты напрямую привязаны к состоянию клиента, которое хранится в одном экземпляре класса ModelLocator. Таким образом, как только модель изменятся в классе ModelLocator, все view-компоненты, привязанные к модели, получают уведомления (через внутренний механизм Flex Data Binding) и перерисовывают новые данные клиента.
Cairngorm предоставляет маркерный интерфейс для паттерна Model Locator и в Магазине Cairngorm мы имеем единственный класс, который описывает это интерфейс: org.nevis.cairngorm.samples.store.model.ModelLocator.
Рассмотрим несколько моментов использования класса ModelLocator. В Магазине Cairngorm есть понятие «выбранный продукт». Это предмет, который выбрал пользователь. Для этого наше приложение хранит в модели указатель на экземпляр класса ProductVO в атрибуте selctedItem, при этом панель ProductDetails использует его для отображения информации об этом продукте, а также он используется при добавлении продукта в корзину, когда пользователь нажимает кнопку «Add to Cart».
Вот так это объявлено в классе ModelLocator:

public static var selectedItem : ProductVO;

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

public static var products : Array; // содержит объекты ProductVO

Также обратите внимание на то, как приложение хранит константы в ModelLocator. В нашем приложении эти константы определяют состояние приложения (которых всего три), следующим образом:

public static var workflowState : Number;
//------------------------------------------------------------
public static var VIEWING_PRODUCTS_IN_THUMBNAILS : Number = 1;
public static var VIEWING_PRODUCTS_IN_GRID : Number = 2;
public static var VIEWING_CHECKOUT : Number = 3;

И наконец у нас есть компонента ShoppingCart, заключающая в себе необходимый функционал по добавлению и удалению продуктов в/из списка товаров для покупки клиентом. Этот объект хранится в атрибуте shoppingCart в ModelLocator и инициализируется в методе initialize().
Делая все атрибуты статическими в паттерне Model Locator вы получаете простую реализацию синглтона. Таким образом, к примеру, вы можете быть уверенным в том, что есть только одна инстанция ShoppingCart для одного клиента.
Единственный экземпляр ModelLocator инициализируется при старте главного приложения. В файле Main.mxml (точка входа в Магазин Cairngorm) у нас есть обработчик события creationComplete для Application:

<mx:Script>
<![CDATA[

    import org.nevis.cairngorm.samples.store.model.ModelLocator;  

    private function onCreationComplete() : Void
    {
        ModelLocator.initialise();
    }

]]>
</mx:Script>

Этот участок гарантирует, что все состояние клиента (например, его корзина) будет проинициализировано при старте приложения.
Дальше мы остановимся на ModelLocator поподробней. Но чтобы возбудить ваш интерес, скажу, что создавая свои компоненты, которые полагаются на данные на клиенте, вы не изобретаете запутанные способы, как достать эти данные, а просто привязываете эти компоненты к состоянию клиента через синглтон ModelLocator, в любой точке вашего приложения.
В качестве примера у нас есть компонента SideArea.mxml, содержащая компоненты ProductDetails и ShoppingCart. Как вы увидите в третьей части, ProductDetails требует выбранного продукта, чтобы отобразить его имя, описание, цену и картинку. Поскольку выбранный продукт сохраняется в ModelLocator в атрибуте selectedItem как экземпляр ProductVO, вы можете передать его в компоненту ProductDetails следующим образом:

<details:ProductDetails
        id="productDetailsComp"
        width="100%" height="325"
        currencyFormatter="{ ModelLocator.currencyFormatter }"
        selectedItem="{ ModelLocator.selectedItem }"
        addProduct="addProductToShoppingCart( event )" />

Выделенная линия показывает, что в классе ProductDeatils есть атрибут по имени selectedItem, которые является экземпляром ProductVO. Мы только что использовали связывание данных (механизм фигурных скобок), чтобы гарантировать обновление ProductDetails как только изменяется значение selectedItem в ModelLocator. Я расскажу об этом механизме больше в третьей части.

ЧТО ДАЛЬШЕ

В этой статье я определил проблемы, которые возникают в насыщенных интернет-приложениях, независимо от проблемной области приложения. Я буду использовать их для того чтобы подробно описать модель разработки с Caringorm.
В частности, я выдвинул первую проблему как: хранение состояния на клиенте. Я также отметил взаимозаменяемость понятий «состояние», «модель», «данные клиента». RIA приложения позволяют вам освободится от парадигмы запрос-ответ для HTML веб-приложений. Они позволяют вам хранить данные на клиенте также как при работе с приложением. Отсюда вам будет понятна определенная ответственность за это состояние клиента.
В веб-приложениях ответственность за целостность и согласованность данных возлагается на сервер – между слоем интеграции и бизнес уровнем. Однако вы понимаете, что поскольку объект модели все равно уже есть на клиенте, вы можете использовать возможности Flex для отображения данных между ActionScript и Java, и хранить соответствующие друг-другу объекты как на сервере так и на клиенте. И даже без этого, представляя состояние клиента типизированными объектами с семантическим смыслом – нежели бездумными массивами строк, чисел и булевых – сильно улучшает читаемость кода программы и облегчает его поддержку.
Поэтому я включил в Cairngorm паттерн Value Object (VO, объект-значение), также известный в мире программистов как паттерн DTO (Data Transfer Object).
Имея осмысленный способ представления объектной модели клиента, будет гарантией того, что программистам будет понятно где хранить состояние клиента, как разделить данные клиента между различными компонентами (которые сами по себе могут иметь различный способ отображения данных), и как обеспечить вид клиента самыми последними данными его состояния.
В следствие этого, я представил паттерн Model Locator, впервые появившийся в Cairngorm, как решение того что состояние сохраняется в единственном классе, который доступен в любой точке Flex-приложения. Синглтон Model Locator гарантирует отсутствие дубликатов состояния клиента, дает большим командам разработчиков логичное место для хранения состояния приложения как объетов-значений, одновременно с этим использую мощный механизм связывания данных во Flex, чтобы гарантировать уведомление Вида когда Контроллер меняет Модель.
В третьей части я сосредоточу вас на Виде. Перед тем как подробно изучать фреймворк Cairngorm, я расскажу более подробно о том, как использовать Flex Data Binding с состоянием хранимым в ModelLocator.

Deferred creation of ViewStack children

Сен 16

Задача: ускорить процесс запуска приложения, путем создания только нужных контейнеров в multiview контейнерах (ViewStack, TabNavigator, Accordion).

По-умолчанию creationPolicy="auto", но иногда хочется поставить им вообще "none" и взять под свой контроль процесс создания чилдов.

Решение: создавать дочерние контейнеры из их декспритора.

Код:

<mx:Application
	xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="vertical"
	creationComplete="{bBar.selectedIndex = -1}">

	<mx:Script>
		<![CDATA[
			import mx.core.UIComponentDescriptor;
			import mx.formatters.SwitchSymbolFormatter;
			import mx.events.ItemClickEvent;
			import mx.controls.Alert;

			public var c : int = 0;

			private function println (str : String) : void
			{
				output.text += String(c++) + ": " + str + "\n";
			}

			private function onItemClick (event : ItemClickEvent) : void
			{
				var childDescriptor : UIComponentDescriptor = vstack.childDescriptors[event.index];
				var selectedChild : * = vstack.getChildByName(childDescriptor.id);

				if (selectedChild == null)
				{
					selectedChild = vstack.createComponentFromDescriptor(childDescriptor, false);
					vstack.selectedChild = selectedChild;
					vstack.validateNow();
				}
				else
				{
					vstack.selectedChild = selectedChild;
				}
			}
		]]>
	</mx:Script>

	<mx:ToggleButtonBar
		id="bBar"
		itemClick="onItemClick(event)">
		<mx:dataProvider>
			<mx:String>Canvas 1</mx:String>
			<mx:String>Canvas 2</mx:String>
			<mx:String>Canvas 3</mx:String>
		</mx:dataProvider>
	</mx:ToggleButtonBar>

	<mx:ViewStack
		id="vstack"
		width="400"
		height="250"
		creationPolicy="none">

		<mx:Canvas
			id="c1"
			label="Canvas 1"
			backgroundColor="#CC9955"
			initialize="println('canvas 1 inited')"
			creationComplete="println('canvas 1 created')"
			show="println('Show Canvas #1')">
			<mx:Label
				text="Canvas #1" />
			<mx:Button
				id="b1"
				top="20"
				label="button 1" />
		</mx:Canvas>

		<mx:Canvas
			id="c2"
			label="Canvas 2"
			backgroundColor="#99CC55"
			initialize="println('canvas 2 inited')"
			creationComplete="println('canvas 2 created')"
			show="println('Show Canvas #2');">
			<mx:Label
				text="Canvas #2" />
			<mx:Button
				id="b2"
				top="20"
				label="button 2" />
		</mx:Canvas>

		<mx:Canvas
			id="c3"
			label="Canvas 3"
			backgroundColor="#9955CC"
			initialize="println('canvas 3 inited')"
			creationComplete="println('canvas 3 created')"
			show="println('Show Canvas #3')">
			<mx:Label
				text="Canvas #3" />
			<mx:Button
				id="b3"
				top="20"
				label="button 3" />
		</mx:Canvas>

	</mx:ViewStack>

	<mx:TextArea
		id="output"
		width="400"
		height="250"
		editable="false" />

</mx:Application>

Можно заметить, что самый первый показанный контейнер не диспатчит ивент show - так работает ViewStack…

О creationPolicy (none, auto, all, queued): LiveDocs: About the creationPolicy property

Во Флексе есть два вида контейнеров: singleview и multiview.

Для multiview:
- none - не создававть ничего;
- all - создать все чилды (и их чилды рекурсивно). очень жестко;
- auto - создать только первоначальный контейнер (и его чилды), т.е. тот что указан в selectedIndex/selectedChild или самый первый если не указано. остальные контейнеры будут инстатиированы но не инициированы, при этом их чилды не будут созданы, естественно события initialize и creationComplete не опубликуются.

Для singleview контейнера (VBox, HBox, Canvas) значение all/auto и none - соответственно создавать автоматически чилды (и чилды их чилдов - рекурсивно) или нет. В данном случае all и auto равнозначны. Значение queued - позволяет управлять очередностью создания с помощью тега creationIndex. Вот небольшой пример для иллюстрации:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Panel
    	creationPolicy="queued"
    	initialize="{trace('panel 1 inited')}"
    	creationComplete="{trace('panel 1 created')}">
        <mx:Button
        	label="Button 1.1"
        	initialize="{trace('button 1.1 inited')}"
        	creationComplete="{trace('button 1.1 created')}"/>
        <mx:Button
        	label="Button 1.2"
        	initialize="{trace('button 1.2 inited')}"
        	creationComplete="{trace('button 1.2 created')}" />
    </mx:Panel>

    <mx:Panel
    	creationPolicy="queued"
    	creationIndex="0"
    	initialize="{trace('panel 2 inited')}"
    	creationComplete="{trace('panel 2 created')}">
        <mx:Button
        	label="Button 2.1"
        	initialize="{trace('button 2.1 inited')}"
        	creationComplete="{trace('button 2.1 created')}"/>
        <mx:Button
        	label="Button 2.2"
        	initialize="{trace('button 2.2 inited')}"
        	creationComplete="{trace('button 2.2 created')}" />
    </mx:Panel>

    <mx:Panel
    	creationPolicy="queued"
    	initialize="{trace('panel 3 inited')}"
    	creationComplete="{trace('panel 3 created')}">
        <mx:Button
        	label="Button 3.1"
        	initialize="{trace('button 3.1 inited')}"
        	creationComplete="{trace('button 3.1 created')}" />
        <mx:Button
        	label="Button 3.2"
        	initialize="{trace('button 3.2 inited')}"
        	creationComplete="{trace('button 3.2 created')}" />
    </mx:Panel>
</mx:Application>

Вывод программы:
button 2.1 inited
button 2.2 inited
panel 2 inited
button 2.1 created
button 2.2 created
panel 2 created
button 1.1 inited
button 1.2 inited
panel 1 inited
button 1.1 created
button 1.2 created
panel 1 created
button 3.1 inited
button 3.2 inited
panel 3 inited
button 3.1 created
button 3.2 created
panel 3 created