Наверно, следовало бы сначала описать своё понимание паттерна «Инвалидация чего-либо» во Flex. Может быть напишу статью о полезности такого подхода в разработке визуальных компонентов. В общем, возможно, вы часто используете пару методов invalidateProperties() / commitProperties() для отложеного выполнения логики set методов свойств ваших компонентов. Эта пара определена в базовом для всех Flex контролов классе UIComponent.
Краткий смысл паттерна в том, что мы ожидаем присваивание значений набору свойств компонента в течение одного кадра Flash Player-а, а последующюю логику обработки этих значений переносим на следующий кадр. Это может повысить производительность приложения, а так же навести порядок в обработке значений связанных между собой свойств компонента.
Полагаю, что инвалидация свойств компонентов во Flex практикуется потому, что большинство компонентов инстанциируется посредством MXML и порядок присваивания свойств спрогнозировать невозможно.
Но, на практике, возникаются ситуации, когда подобный механизм нужен и в невизуальных объектах, например элементах модели бизнес-логики, которые приходят от сервера.
Я подготовил простой пример, который демонстрирует суть задачи.
Допустим у нас есть класс модели OpportunityDTO, экземпляры которого получаются от сервера и создаются AMF десериализатором Flash Player-а, который нам не подконтролен. В нём есть два свойства date, children, в момент присваивания значений которых, нам нужна дополнительная обработка. Эта обработка должна выполняться только тогда, когда оба свойства получили свои значения. Типичный случай для паттерна инвалидации свойств.
package com.tearaway_tea.model
{
import flash.events.EventDispatcher;
import mx.collections.ArrayCollection;
import org.goverla.interfaces.IPropertiesInvalidable;
import org.goverla.managers.PropertiesInvalidationManager;
public class OpportunityDTO extends EventDispatcher implements IPropertiesInvalidable
{
public function get date() : Date
{
return _date;
}
public function set date(value : Date) : void
{
_date = value;
_dateChanged = true;
invalidateProperties();
}
public function get children() : ArrayCollection
{
return _children;
}
public function set children(value : ArrayCollection) : void
{
_children = value;
_childrenChanged = true;
invalidateProperties();
}
public function invalidateProperties() : void
{
PropertiesInvalidationManager.instance.invalidateProperties(this);
}
public function commitProperties() : void
{
if (_dateChanged && _childrenChanged)
{
_dateChanged = false;
_childrenChanged = false;
for each (var item : ItemDTO in children)
{
item.name += "/" + date.toString();
}
}
}
private var _date : Date;
private var _dateChanged : Boolean;
private var _children : ArrayCollection;
private var _childrenChanged : Boolean;
}
}
Для решения этой задачи я предлагаю использовать синглтон PropertiesInvalidationManager, который работает по образу и подобию флексовского LayoutManager-а.
package org.goverla.managers
{
import mx.rpc.AsyncDispatcher;
import org.goverla.interfaces.IPropertiesInvalidable;
public class PropertiesInvalidationManager
{
public static function get instance() : PropertiesInvalidationManager
{
if (_instance == null)
{
_instance = new PropertiesInvalidationManager();
}
return _instance;
}
private static var _instance : PropertiesInvalidationManager;
public function invalidateProperties(target : IPropertiesInvalidable) : void
{
if (_targets.indexOf(target) < 0)
{
_targets.push(target);
if (_dispatcher == null)
{
_dispatcher =
new AsyncDispatcher(commitProperties, [], 0);
}
}
}
public function commitProperties() : void
{
for each (var target : IPropertiesInvalidable in _targets)
{
target.commitProperties();
}
_targets = [];
_dispatcher = null;
}
private var _targets : Array = [];
private var _dispatcher : AsyncDispatcher;
}
}
Суть очевидна. Менеджер получает ссылку на объект, который нуждается в инвалидации свойств, ставит его в очередь и на следующем кадре вызывает у него commitProperties(). Для откладывания выполнения используется флексовский AsyncDispatcher, который основан на Timer.
Для удобства использования, можна создать какой-то базовый класс, который будет имплементировать интерфейс IPropertiesInvalidable.
Что-то я не совсем в ехал… зачем это необходимо для модели?
Ну, представь. Мы получаем от сервера какой-то экземпляр DTO (Data Transfer Object), что происходит в этот момент? Десериализатор создаёт экземпляр класса, и по очереди присваивает значения в свойства. Так вот, эта очередь не предсказуема (как и присваивание значений для свойств визуального объекта в MXML). И, когда задача предусматривает дополнительную обработку в момент присваивания этих свойств, порой выгодно её решить используя инвалидацию.
Я не работал c AMF… по видимому не совсем в теме =)
Насчет механизма валидации/инвалидации у UIComponent’ов, так он скорей сделан для решения одной из проблем :разделения во времени момента асайна свойств ui-компонета и обработки этих свойств дочерними компонентами. Сделано это потому, что после инстанциирования ui-компонета его дети ещё не созданы. Ну а вторая причина указана вами. Скорей всего есть ещё плюсы данного механизма :)
Всё правильно вы говорите. Плюсы, несомненно, есть. Но, есть и минусы, при использовании описанного выше подхода, экземпляр DTO будет готов к использованию только на следующем кадре, это может быть проблемой для некоторых алгоритмов бизнес-логики, которые выполняются в синхронном потоке одного кадра плеера.
Отличная тема. Синглтончик бы еще убрать и вообще зашибись :)
Ну, конечно! Это же анти-паттерн. Класс PropertiesInvalidationManager можно инстанциировать в MATE, а ссылку на инвалидируемый объект отправлять событием через матешный диспатчер, напрямую в мапы.
Но, думаю, синглтон, именно в этом случае, удачнее. =)
Не в этом дело, что “антипаттерн”. Вернее, в этом, но вот что такого в нем антипаттернистого? А все просто. Вот захочется тебе твой класс протестировать. И все. И не замочишь ты свой синглтон уже никак. Придется с ним жить. И вовсе не факт, что в тесте тебе бы не пригодился какой-нибудь заменитель, не страдающий асинхронностью или еще с какими-нибудь другими свойствами.
Ну, вообще-то данный подход не предполагает иного использования при юнит-тестировании, кроме как откладывания выполнения на следующий кадр. По-этому, то что вы описали, скорее проблема высосаная из пальца =). И тем не менее, может быть предложите другой, более элегантный способ указывания того факта, что логика объекта нуждается в отложенном выполнении? Фабричный метод?
P.S. Мне просто интересно, была ли у вас нужда, при юнит-тестировании заменять какие-либо флексовские синглтоны, на что-то другое?
Ну практически все флексовские синглтоны относятся к GUI, с модульным тестированием которого не так все просто. Здесь же речь идет о классе, относящемся к модели. Его логику сам Бог велел тестировать. Просто я к тому, что PropertiesInvalidationManager туда стоит инжектировать по интерфейсу и тогда все будет зашибись :)То есть класс практически не изменится, просто у него появится сеттер для менеджера.
http://skovalyov.blogspot.com/2007/01/invalidation-mechanism-for-non-ui.html
Моё решение изящнее и элегантнее в 100 раз, не позорься =).
Твоё решение достойно разве что code_wtf.
Я так понимаю, что все свои решения ты, впечатлившись, черпаешь именно оттуда? =)
Да ладн, тоже нормальный подход. Только Timer(1, 1); – одна миллисекунда может оказаться не оптимальной величиной для универсального решения.
Ну я шутил конечно-же. Но тем не менее, решение Ковалёва предполагает наследование от специального базового класса. А мой вариант может использоваться где угодно =), на любом уровне иерархии цепочки наследования классов.
Да, соглашусь что наследование здорово помешает. Но по мне и глобальный класс тоже имеет промахи. Я б его не глобальным а “локальным” сделал. С применением композиции.
Инвалидация свойств оч полезный метод, я тоже жалел что нет такого готового механизма для невизуальных компонентов.
Сильно извеняюсь, не нашел такого класса mx.rpc.AsyncDispatcher. Плохо искал?: http://livedocs.adobe.com/flex/3/langref/mx/rpc/package-detail.html
Этот класс обозначен метаданными [ExcludeClass]. По-этому, он не попадает в список доступных ни в помощи ни в code-insight. Но использовать его можно без проблем, если вы указываете его импорт.
Я нашёл его в исходниках флекса случайно, когда эксперементировал с ремоутингом.
Ясно, спасибо. А что, он ловит реальный следующий кадр?
Он, так же основан на Timer, потому как это единственный способ в флеше перенести выполнение алгоритма на следующий кадр.
А интервал высчитывается исходя из текущего Frame Rate?
Да, на сколько мне известно, Frame Rate влияет на количество заходов в “кадр” за одну секунду.
Если быть точнее то FrameRate влияет на максимальное количество заходов в “кадр” за одну сек. Следовательно интервал частично высчитывается из текущего FrameRate.
А вообще прикольная штука AsyncDispatcher, можно будет заюзать денибудь.
I would like to say this is a pattern. Very useful thing.