Flash Ripper RSS Readers

Выполнение функции в заданной области видимости с помощью класса mx.utils.Delegate (или: Передача событий от любого объекта к любому объекту)

Хорошая новость, а точнее, даже несколько. В последнее обновление для среды разработки Flash MX 2004 вошел новый класс mx.utils.Delegate. Его назначение -- избавить разработчиков от еще одной старой головной боли, связанной с обработкой событий. Это была старая проблема с областью видимости: вызывая функцию-обработчик события компонента (или, например, XML-объекта, или мувиклипа), мы автоматически попадаем в область видимости этого компонента (XML-объекта, мувиклипа и т.д.), а это означает, что ключевое слово this внутри такой функции будет указывать на вызвавший событие компонент (XML-объект, мувиклип..), а не на класс или мувиклип, которому принадлежит функция-обработчик. Но чаще всего внутри функции-обработчика нам нужна как раз область видимости содержащего ее класса или мувиклипа, а не вызвавшего событие компонента (XML-объекта, мувиклипа и т.д.)!

Как раз для решения это проблемы и предназначен класс mx.utils.Delegate. Изначально, класс Delegate был выпущен компанией Macromedia с целью прямой передачи событий компонентов в использующие их классы (раньше это приходилось делать с помощью всевозможных ухищрений по передаче событий.) Но, как мы сейчас увидим, этот полезный класс можно с успехом использовать и для обработки любых других, самых обычных событий.

Итак, первичная задача класса Delegate -- передать событие от компонента к использующему его объекту; вот синтаксис, изначально предлагаемый Macromedia (случай с компонентом):

import mx.utils.Delegate;
имяЭкземпляра.addEventListener("названиеСобытия", Delegate.create(областьВидимости, функция));

(См.: Оригинальная статья на LiveDocs по делегированию событий компонентов.)

Прекрасная возможность! Но, оказывается, с помощью класса Delegate можно делегировать не только события компонентов, но и любые, самые привычные и простые события, например, такие, как события кнопок или объекта XML. В качестве практического примера рассмотрим распространенную ситуацию: мы загружаем некий XML и хотим, что по окончании его загрузки была вызвана функция-обработчик, из которой мы могли бы получить прямой доступ к классу или мувиклипу, содержащему эту функцию.

В этом случае мы можем использовать следующий, еще более простой синтаксис:

import mx.utils.Delegate;
xmlData.onLoad=Delegate.create(this, processXML);

где this указывает на целевую область видимости (это может быть конкретный экземпляр класса или мувиклип, именно на него будет указывать ключевое слово this в вызываемом методе или функции-обработчике, processXML -- это метод-обработчик данного класса (или функция на таймлайне мувиклипа), который(ая) будет вызван(а) по окончании загрузки. Вот полный пример исходного кода:

// Это некоторый класс, использующий загружаемый XML, код содержитя в файле "SomeClassThatUsesLoadedXML.as":

import mx.utils.Delegate;

class SomeClassThatUsesLoadedXML{ // with Delegate class

private var inited:Boolean=false;
private var xmlData:XML;

function SomeClassThatUsesLoadedXML(){
if(!inited){
init();
}
}
private function init(){
xmlData=new XML();
xmlData.onLoad=Delegate.create(this,processXML);
inited=true;
}
private function processXML():Void{
trace("Событие onLoad вызвано на объекте: "+this + "\nXML данные: "+ xmlData);
}
public function loadXML(urlPath:String):Void{
xmlData.load(urlPath);
}
public function toString(){
return "[SomeClassThatUsesLoadedXML]";
}
}

//Пример использования, код в fla-файле:
var experimental:SomeClassThatUsesLoadedXML=new SomeClassThatUsesLoadedXML();
experimental.loadXML("content/xml/data.xml");

// Вывод в окне Output:
Событие onLoad вызвано на объекте: [SomeClassThatUsesLoadedXML]
XML данные:
Item 1

Разве это не хорошо? Спасибо ребятам из Macromedia!
//Обратите внимание: класс mx.utils.Delegate можно использовать как в AS1, так и в AS2, в любом предпочитаемом вами стиле кодирования.

Писал Rost, 9 Август 2004 20:12

Найдены баги:

Приятное известие!

Макромедиа шаг за шагом своими методами реализует стандарты ООП, остаеться только идти в ногу!

George - 10 Август 2004 10:08

грамотно! =)

(четвертое и пятое предложение первого абзаца в этой статье - это не для слабых духом =)) )

Nox Noctis - 10 Август 2004 16:58

возможность несомненно полезная.

тока это, не совсем понятно как это связано с "реализацией стандартов ООП"... =)

Colin Moock - 10 Август 2004 22:41

я вот ещё о чем подумал. может я канешно ошибаюсь, но думаю что правильнее это всё дело будет назвать не "передачей событий другому объекту", а так, как это называется в мануале —"The Delegate class lets you run a function in a specific scope" — "выполнение функции в заданной области видимости". ну или в "заданном контексте". имхо, это понятнее описывает сущность происходящего явления.

Colin Moock - 10 Август 2004 23:16

оффтоп:
не первый раз встречаю такую странную конструкцию:

function SomeClassThatUsesLoadedXML(){
if(!inited){
init();
}
}

такие же встречаются и в моей книге "Essential Actionscript 2.0". может мне кто нибудь объяснить, почему бы не написать так:

function SomeClassThatUsesLoadedXML(){
// содержимое функции init()
}

?

Colin Moock - 11 Август 2004 17:22

я думаю просто для красоты структуры. чтобы не совмещать конструктор с инициализационной функцией.

по крайней мере других вариантов что-то не придумывается =)

Nox Noctis - 11 Август 2004 20:12

Nox Noctis, спасибо за ответ, я немного уточню:

Уважаемый Колин, отвечаю на твой вопрос: из назначения функции инициализации следует, что эта функция должна быть выполнена ровно один раз. Для этого ты и используешь следующую конструкцию:

function SomeClassThatUsesLoadedXML(){
if(!inited){
init();
}
}
private function init(){
inited=true;
}

-- то есть, чтобы избежать повторной инициализации, в функции инициализации происходит присвоение "inited=true", и эта функция сама исключает возможность своего повторного вызова. Следовательно, подобная конструкция гарантирует правильную, однократную инициализацию экземпляра класса. Хотя, можно было бы делать иначе: можно быо бы проводить проверку "if(!inited)" не внутри конструктора, а в самом начале функции "init".

Надеюсь, ты получил желаемое ;-)

Голос из зала - 11 Август 2004 20:42

Nox Noctis:
>>(четвертое и пятое предложение первого абзаца >>в этой статье - это не для слабых духом =)) )

Да, я с тобой согласен, получилось довольно поморочено.. но и смысл в преложении заложен тоже весьма непростой, не правда ли?

Слушай, помоги упростить саму фразу, а?

;-)

Голос из зала - 11 Август 2004 20:47

Colin Moock, спасибо за замечание по поводу названия статьи. Я уже отредактировал его в соответствии с высказанными тобой резонными соображениями).

Голос из зала - 11 Август 2004 20:51

Уважаемый Голос из зала!

Всё что ты написал, ессно, правильно. Но. К превеликому сожалению, я не понял почему в примере Роста, ровно как и в примерах в моей книге, мы не пишем так:


function SomeClassThatUsesLoadedXML(){
xmlData=new XML();
xmlData.onLoad=Delegate.createthis,processXML);
}

? Ведь, насколько я понимаю, конструктор будет выполнен гарантированно токмо один (1) раз для экземпляра. И не нужно городить никаких булевых флагов. На то он и конструктор...

Вот код, с абсолютно идентичной функциональностью:


import mx.utils.Delegate;
class SomeClassThatUsesLoadedXML{
private var xmlData:XML;
function SomeClassThatUsesLoadedXML()
{
xmlData = new XML()
xmlData.onLoad = Delegate.create(this, processXML);
}
private function processXML():Void
{
trace("Событие onLoad вызвано на объекте: " + this + "\nXML данные: " + xmlData);
}
public function loadXML(urlPath:String):Void
{
xmlData.load(urlPath);
}
public function toString()
{
return "[SomeClassThatUsesLoadedXML]";
}
}

Уважаемый Nox Noctis!

Насколько я могу доверять моим скромным познаниям в программировании, конструктор — это и есть инициализационная функция.

Это и есть главная и единственная его цель в нашем бренном мире... =)

Вот доказательства из мануала:

Constructors are functions that you use to define (initialize) the properties and methods of a class.

и из моей книги:

To initialize and perform setup tasks for new objects of a class, we create a constructor function.

Colin Moock - 11 Август 2004 23:18

по поводу 4-го и 5-го предложений. 5-е, имхо, совершенно понятное и простое предложение. 4-е можно попробовать сократить до такого:

Это была старая проблема с областью видимости: в теле функции-обработчика события компонента (или, например, XML-объекта, или мувиклипа) ключевое слово this будет указывать на этот компонент (XML-объект, мувиклип..), а не на класс или мувиклип, которому принадлежит функция-обработчик.

Colin Moock - 11 Август 2004 23:39

а я ведь не спорю =)

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

у меня была версия, что это как-то может быть связано с организацией наследования - к примеру если есть перегруженная функция init, которая при создании объекта должна вызваться только в конструкторе над-класса и не должна вызваться в конструкторе дочернего класса... но это какие-то пляски с бубном =)

вобщем мне тоже не особо понятно зачем такое.

Nox Noctis - 11 Август 2004 23:45

>>Это и есть главная и единственная его цель в нашем бренном мире... =)

хе. если бы мы взяли АС1, где конструктор над-класса не вызвается сам по себе при создании экземпляра объекта дочернего класса, то сия конструкция вполне могла бы иметь смысл: не переинициализировать экземпляр при вызове super() (это всё конечно опять про перегруженный init =) )

может эти строки в коде АС2 - это пережиток старого шестерочного?

(или я снова чего-то не понял... =) )

Nox Noctis - 11 Август 2004 23:52

пока писал всё предыдущее, подумал: а нафига Delegate когда есть банальный apply, решающий проблему изменения области видимости ничуть не хуже? =)

способов туча, и все они, мне кажется, гораздо проще чем использование специального класса Delegate.

к примеру можно так:

	private function init() {
xmlData = new XML();
xmlData.scope = this;
xmlData.feedBack = processXML;
xmlData.onLoad = function() {
this.feedBack.apply(this.scope);
};
inited = true;
}

...это "сложнее" на две строчки. зато нет никакого лишнего класса.

или же прямо в processXML вписать scope, а в теле processXML написать with (arguments.cellee.scope) { ... }

Nox Noctis - 12 Август 2004 0:08

ХА =)
оцените это текст:

// Это некоторый класс, использующий загружаемый XML, код содержитя в файле "SomeClassThatUsesLoadedXML.as":
import mx.utils.Delegate;
class SomeClassThatUsesLoadedXML{ // with Delegate class
private var inited:Boolean=false;
private var xmlData:XML;
function SomeClassThatUsesLoadedXML(){
if(!inited){
init();
}
}
private function init(){
xmlData=new XML();
xmlData.onLoad=Delegate.create(this,processXML);
inited=true;
}
private function processXML():Void{
trace("Событие onLoad вызвано на объекте: "+this + "\nXML данные: "+ xmlData);
}
public function loadXML(urlPath:String):Void{
xmlData.load(urlPath);
}
public function toString(){
return "[SomeClassThatUsesLoadedXML]";
}
}
//
//Пример использования, код в fla-файле:
Function.prototype.$apply = Function.prototype.apply;
Function.prototype.apply = function(scope, args) {
trace("apply called!");
this.$apply(scope, args);
};
//
var experimental:SomeClassThatUsesLoadedXML=new SomeClassThatUsesLoadedXML();
experimental.loadXML("content/xml/data.xml");

угадайте что =))

какие выводы?

зачем нужен целый класс, я лично вообще перестал понимать =))

Nox Noctis - 12 Август 2004 0:20

ай ай =(
да простят меня благородные доны, что-то в предыдущем посте с разметкой не заладилось совсем...

Nox Noctis - 12 Август 2004 0:22

Так грузит.Нам бы чо попроще....:(((((((((

dfdf - 12 Август 2004 2:19

На мой взгляд булевые флаги это действительно пережитки AS1 (например, часто применяються в циклах таймлайна, для единственной инициализации переменных или классов).

А насчет реализации события в определенной области видимости, это скорее дело стиля программирования. Можно кодировать линейно, а можно и при помощи классов (что макромедиа и сделала в Delegate, довольно удобно).

George - 12 Август 2004 11:48

выше есть пример, доказывающий что Delegate работает через Function.apply - то есть вчистую вызывает эту же функцию... при таких условиях не понятно зачем целый класс: это примерно то же самое что сделать отдельный класс ради арифметических действий с целыми числами...

Nox Noctis - 12 Август 2004 11:53

Уважаемый Nox Noctis!

Вы абсолютно правы — Delegate использует apply. Это хорошо заметно по этому коду:


class mx.utils.Delegate extends Object
{
static function create(obj:Object, func:Function):Function
{
var f = function()
{
var target = arguments.callee.target;
var func = arguments.callee.func;
return func.apply(target, arguments);
};
f.target = obj;
f.func = func;
return f;
}
function Delegate(f:Function)
{
func = f;
}
private var func:Function;
function createDelegate(obj:Object):Function
{
return create(obj, func);
}
}

Но я, всё же, думаю что что с классом таки лучше. И дело не только в том что код короче на две строчки, но в том, что он красивее, структурированный и понятней. Тем более классик то маленький... =)


зы: разметка накрылась потому что не надо пустых строк между строками кода вставлять... ;)

Colin Moock - 12 Август 2004 13:04

Уважаемый Nox Noctis!
Доказывать что Delegate работает через Function.apply собственно и не надо, достаточно взять и открыть его код:


/**
The Delegate class creates a function wrapper to let you run a function in the context
of the original object, rather than in the context of the second object, when you pass a
function from one object to another.
*/
class mx.utils.Delegate extends Object
{
/**
Creates a functions wrapper for the original function so that it runs
in the provided context.
@parameter obj Context in which to run the function.
@paramater func Function to run.
*/
static function create(obj:Object, func:Function):Function
{
var f = function()
{
var target = arguments.callee.target;
var func = arguments.callee.func;
return func.apply(target, arguments);
};
f.target = obj;
f.func = func;
return f;
}
function Delegate(f:Function)
{
func = f;
}
private var func:Function;
function createDelegate(obj:Object):Function
{
return create(obj, func);
}
}

То что класс не нужен весьма ошибочно! Если есть одна функция в одном классе я бы и не спорил, можно обойтись и так. Но имея десятки функций и десятки классов, такой подход ручного прописывания кода весьма не оптимален. Хочу напомнить что по спецификации AS2 функциональность класса физически создается один раз, а реализации (экземпляры) класса только обращаються к ней как к единой библиотеке.
Таким образом мы не засоряем код и делаем приложение компактнее и оптимальнее.

George - 12 Август 2004 13:10

>>Доказывать что Delegate работает через Function.apply собственно и не надо

дада, вы правы конечно =) я не сообразил сразу =)

Nox Noctis - 12 Август 2004 13:18

Мне не дает покоя вопрос, заданный Колином: зачем нужна конструкция типа

function SomeClassThatUsesLoadedXML(){
if(!inited){
init();
}
}
private function init(){
inited=true;
}

Я точно помню, что есть рабочие, связанные со спецификой флэша ситуации (таймлайн), когда именно эта конструкция спасала от повторной инициализации клипа (точнее, класса, связаннного с физическим клипом). Но, как назло, у меня сейчас все в порядке с флэшом, так что я не могу найти пример, когда эта конструкция спасает от ошибок.. но я найду и опубликую здесь. А пока можете просто поверить на слово: данная конструкция -- это не пережиток AS1, а реальная необходимость.

Голос из зала - 26 Август 2004 22:23

C нетерпением будем ждать... =)

Colin Moock - 5 Сентябрь 2004 18:32

Спасибо!
Уже употребил mx.utils.Delegate класс в проекте.

Что бы там не говорили, а пользоватся классом удобнее, чем вызывать напрямую apply и arguments.cellee (и то, и другое недолюбливаю).

JabbyPanda - 5 Сентябрь 2004 20:18



Это запись из категории 'Coding'. 10 еще cвежих:

Архивы по категориям:

3D-18, Adobe AIR-30, Animation-1, Apache Ant-1, Architecture-1, ARP-1, Art-25, Articles-26, AS3-52, Books-7, Business-3, Cairngorm-2, CI-1, Classes-10, Coding-30, Community-113, Components-19, Contests-28, Cool-Job-5, Debug-18, Design-26, Development-84, EMO-1, Events-13, Extensions-2, FAQ-8, FDS-1, Flash and html-5, Flash Player-35, Flash Updates-8, Flash-scene-1, flash10-4, FlashLite-2, Flex-30, Flex 2-80, Flickr-1, FMS-1, FPUG-46, frameworks-1, Games-11, Good Job!-35, HaXe-14, Health-2, Humor-10, Ideas-13, JavaScript-1, Job-26, JSFL-8, Links-2, Linux-1, Maps-1, Math-8, Money-11, MXML-1, Open Source-15, Optimization-2, Patterns-2, Personalities-27, Politics-1, Preloading-3, Productivity-9, PureMVC-10, Pv3d-1, Rafpug-4, Red5-3, Remoting-11, Resources-21, Ruby-6, SAAS-1, Security-11, SEO-8, Silverlight-5, Sound-3, Strategy-120, Tamarin-1, Tools-113, Training-2, Trash-8, URAFPUG-13, Urgent-1, Usability-6, Video-6, VoIP-5, Wallop-1, Wishlist-2, Архив всех записей (большой)

За последние месяцы:

Июл 2008: Международная встреча разработчиков URAFPUG завершена, URAFPUG - трансляция студии Flex-фреймворка Mate, весь Июл

Июн 2008: Попытка предварительных выводов о встрече аниматоров, Онлайн трансляция встречи аниматоров в Донецке, весь Июн

Май 2008: Если 3D, то по-взрослому: официальный запрос в Adobe по поводу контроля над мип-маппингом. Нужна ваша поддержка!, В этом году «Russian Flash Awards» пройдет в «космическом стиле», весь Май

Апр 2008: Программирование под флэш платформу. Cтатья (местами спорная), Advanced Flash Components бесплатно раздает все свои AS2-компоненты, весь Апр

Мар 2008: Зарплаты программистов в 2007 году, FlashPhone как технология года? Технология года? В Рунете?, весь Мар

Фев 2008: ЙА ФПУГ — регистрация на первую встречу UAFPUG продолжается, Закулисы Flex и секрет успеха опенсорс-проекта, весь Фев





Примечания:
Статус документа
: в процессе
   2002-2007 Производство: Рост Прибыли · О проекте · Подписка на новости (RSS)