Инвалидация свойств невизуальных объектов

Posted by - April 16, 2009

Наверно, следовало бы сначала описать своё понимание паттерна «Инвалидация чего-либо» во 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.

24 Comments on Инвалидация свойств невизуальных объектов

Respond | Trackback

  1. ez says:

    Что-то я не совсем в ехал… зачем это необходимо для модели?

    • tearaway_Tea says:

      Ну, представь. Мы получаем от сервера какой-то экземпляр DTO (Data Transfer Object), что происходит в этот момент? Десериализатор создаёт экземпляр класса, и по очереди присваивает значения в свойства. Так вот, эта очередь не предсказуема (как и присваивание значений для свойств визуального объекта в MXML). И, когда задача предусматривает дополнительную обработку в момент присваивания этих свойств, порой выгодно её решить используя инвалидацию.

      • ez says:

        Я не работал c AMF… по видимому не совсем в теме =)
        Насчет механизма валидации/инвалидации у UIComponent’ов, так он скорей сделан для решения одной из проблем :разделения во времени момента асайна свойств ui-компонета и обработки этих свойств дочерними компонентами. Сделано это потому, что после инстанциирования ui-компонета его дети ещё не созданы. Ну а вторая причина указана вами. Скорей всего есть ещё плюсы данного механизма :)

        • tearaway_Tea says:

          Всё правильно вы говорите. Плюсы, несомненно, есть. Но, есть и минусы, при использовании описанного выше подхода, экземпляр DTO будет готов к использованию только на следующем кадре, это может быть проблемой для некоторых алгоритмов бизнес-логики, которые выполняются в синхронном потоке одного кадра плеера.

  2. Constantiner says:

    Отличная тема. Синглтончик бы еще убрать и вообще зашибись :)

    • tearaway_Tea says:

      Ну, конечно! Это же анти-паттерн. Класс PropertiesInvalidationManager можно инстанциировать в MATE, а ссылку на инвалидируемый объект отправлять событием через матешный диспатчер, напрямую в мапы.

      Но, думаю, синглтон, именно в этом случае, удачнее. =)

      • Constantiner says:

        Не в этом дело, что “антипаттерн”. Вернее, в этом, но вот что такого в нем антипаттернистого? А все просто. Вот захочется тебе твой класс протестировать. И все. И не замочишь ты свой синглтон уже никак. Придется с ним жить. И вовсе не факт, что в тесте тебе бы не пригодился какой-нибудь заменитель, не страдающий асинхронностью или еще с какими-нибудь другими свойствами.

        • tearaway_Tea says:

          Ну, вообще-то данный подход не предполагает иного использования при юнит-тестировании, кроме как откладывания выполнения на следующий кадр. По-этому, то что вы описали, скорее проблема высосаная из пальца =). И тем не менее, может быть предложите другой, более элегантный способ указывания того факта, что логика объекта нуждается в отложенном выполнении? Фабричный метод?

          P.S. Мне просто интересно, была ли у вас нужда, при юнит-тестировании заменять какие-либо флексовские синглтоны, на что-то другое?

          • Constantiner says:

            Ну практически все флексовские синглтоны относятся к GUI, с модульным тестированием которого не так все просто. Здесь же речь идет о классе, относящемся к модели. Его логику сам Бог велел тестировать. Просто я к тому, что PropertiesInvalidationManager туда стоит инжектировать по интерфейсу и тогда все будет зашибись :)То есть класс практически не изменится, просто у него появится сеттер для менеджера.

  3. Твоё решение достойно разве что code_wtf.

    • tearaway_Tea says:

      Я так понимаю, что все свои решения ты, впечатлившись, черпаешь именно оттуда? =)

      • Racer says:

        Да ладн, тоже нормальный подход. Только Timer(1, 1); – одна миллисекунда может оказаться не оптимальной величиной для универсального решения.

        • tearaway_Tea says:

          Ну я шутил конечно-же. Но тем не менее, решение Ковалёва предполагает наследование от специального базового класса. А мой вариант может использоваться где угодно =), на любом уровне иерархии цепочки наследования классов.

          • Racer says:

            Да, соглашусь что наследование здорово помешает. Но по мне и глобальный класс тоже имеет промахи. Я б его не глобальным а “локальным” сделал. С применением композиции.

  4. Racer says:

    Инвалидация свойств оч полезный метод, я тоже жалел что нет такого готового механизма для невизуальных компонентов.

    Сильно извеняюсь, не нашел такого класса mx.rpc.AsyncDispatcher. Плохо искал?: http://livedocs.adobe.com/flex/3/langref/mx/rpc/package-detail.html

    • tearaway_Tea says:

      Этот класс обозначен метаданными [ExcludeClass]. По-этому, он не попадает в список доступных ни в помощи ни в code-insight. Но использовать его можно без проблем, если вы указываете его импорт.

      Я нашёл его в исходниках флекса случайно, когда эксперементировал с ремоутингом.

      • Racer says:

        Ясно, спасибо. А что, он ловит реальный следующий кадр?

        • tearaway_Tea says:

          Он, так же основан на Timer, потому как это единственный способ в флеше перенести выполнение алгоритма на следующий кадр.

        • tearaway_Tea says:

          Да, на сколько мне известно, Frame Rate влияет на количество заходов в “кадр” за одну секунду.

          • Стас says:

            Если быть точнее то FrameRate влияет на максимальное количество заходов в “кадр” за одну сек. Следовательно интервал частично высчитывается из текущего FrameRate.

            А вообще прикольная штука AsyncDispatcher, можно будет заюзать денибудь.

  5. I would like to say this is a pattern. Very useful thing.

Respond

Comments

Comments