Хорошая новость, а точнее, даже несколько. В последнее обновление для среды разработки Flash MX 2004 вошел новый класс mx.utils.Delegate. Его назначение -- избавить разработчиков от еще одной старой головной боли, связанной с обработкой событий. Это была старая проблема с областью видимости: вызывая функцию-обработчик события компонента (или, например, XML-объекта, или мувиклипа), мы автоматически попадаем в область видимости этого компонента (XML-объекта, мувиклипа и т.д.), а это означает, что ключевое слово this внутри такой функции будет указывать на вызвавший событие компонент (XML-объект, мувиклип..), а не на класс или мувиклип, которому принадлежит функция-обработчик. Но чаще всего внутри функции-обработчика нам нужна как раз область видимости содержащего ее класса или мувиклипа, а не вызвавшего событие компонента (XML-объекта, мувиклипа и т.д.)!
mx.utils.Delegate
this
Как раз для решения это проблемы и предназначен класс mx.utils.Delegate. Изначально, класс Delegate был выпущен компанией Macromedia с целью прямой передачи событий компонентов в использующие их классы (раньше это приходилось делать с помощью всевозможных ухищрений по передаче событий.) Но, как мы сейчас увидим, этот полезный класс можно с успехом использовать и для обработки любых других, самых обычных событий.
Delegate
Итак, первичная задача класса Delegate -- передать событие от компонента к использующему его объекту; вот синтаксис, изначально предлагаемый Macromedia (случай с компонентом):
import mx.utils.Delegate; имяЭкземпляра.addEventListener("названиеСобытия", Delegate.create(областьВидимости, функция));
(См.: Оригинальная статья на LiveDocs по делегированию событий компонентов.)
Прекрасная возможность! Но, оказывается, с помощью класса Delegate можно делегировать не только события компонентов, но и любые, самые привычные и простые события, например, такие, как события кнопок или объекта XML. В качестве практического примера рассмотрим распространенную ситуацию: мы загружаем некий XML и хотим, что по окончании его загрузки была вызвана функция-обработчик, из которой мы могли бы получить прямой доступ к классу или мувиклипу, содержащему эту функцию.
В этом случае мы можем использовать следующий, еще более простой синтаксис:
import mx.utils.Delegate; xmlData.onLoad=Delegate.create(this, processXML);
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");
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, в любом предпочитаемом вами стиле кодирования.
Приятное известие!
Макромедиа шаг за шагом своими методами реализует стандарты ООП, остаеться только идти в ногу!
грамотно! =)
(четвертое и пятое предложение первого абзаца в этой статье - это не для слабых духом =)) )
возможность несомненно полезная.
тока это, не совсем понятно как это связано с "реализацией стандартов ООП"... =)
я вот ещё о чем подумал. может я канешно ошибаюсь, но думаю что правильнее это всё дело будет назвать не "передачей событий другому объекту", а так, как это называется в мануале —"The Delegate class lets you run a function in a specific scope" — "выполнение функции в заданной области видимости". ну или в "заданном контексте". имхо, это понятнее описывает сущность происходящего явления.
оффтоп: не первый раз встречаю такую странную конструкцию:
function SomeClassThatUsesLoadedXML(){ if(!inited){ init(); } }
такие же встречаются и в моей книге "Essential Actionscript 2.0". может мне кто нибудь объяснить, почему бы не написать так:
function SomeClassThatUsesLoadedXML(){ // содержимое функции init() }
?
я думаю просто для красоты структуры. чтобы не совмещать конструктор с инициализационной функцией.
по крайней мере других вариантов что-то не придумывается =)
Nox Noctis, спасибо за ответ, я немного уточню:
Уважаемый Колин, отвечаю на твой вопрос: из назначения функции инициализации следует, что эта функция должна быть выполнена ровно один раз. Для этого ты и используешь следующую конструкцию:
function SomeClassThatUsesLoadedXML(){ if(!inited){ init(); } } private function init(){ inited=true; }
-- то есть, чтобы избежать повторной инициализации, в функции инициализации происходит присвоение "inited=true", и эта функция сама исключает возможность своего повторного вызова. Следовательно, подобная конструкция гарантирует правильную, однократную инициализацию экземпляра класса. Хотя, можно было бы делать иначе: можно быо бы проводить проверку "if(!inited)" не внутри конструктора, а в самом начале функции "init".
Надеюсь, ты получил желаемое ;-)
Nox Noctis: >>(четвертое и пятое предложение первого абзаца >>в этой статье - это не для слабых духом =)) )
Да, я с тобой согласен, получилось довольно поморочено.. но и смысл в преложении заложен тоже весьма непростой, не правда ли?
Слушай, помоги упростить саму фразу, а?
;-)
Colin Moock, спасибо за замечание по поводу названия статьи. Я уже отредактировал его в соответствии с высказанными тобой резонными соображениями).
Уважаемый Голос из зала!
Всё что ты написал, ессно, правильно. Но. К превеликому сожалению, я не понял почему в примере Роста, ровно как и в примерах в моей книге, мы не пишем так:
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.
по поводу 4-го и 5-го предложений. 5-е, имхо, совершенно понятное и простое предложение. 4-е можно попробовать сократить до такого:
Это была старая проблема с областью видимости: в теле функции-обработчика события компонента (или, например, XML-объекта, или мувиклипа) ключевое слово this будет указывать на этот компонент (XML-объект, мувиклип..), а не на класс или мувиклип, которому принадлежит функция-обработчик.
а я ведь не спорю =)
тем более что дело даже не в формулировке из мануала, а в сути происходящего: ДО срабатывания конструктора объект ну НИКАК не может быть уже инициализирован =)) поэтому сама проверка этого факта - весьма странное мероприятие. согласен полностью =)
у меня была версия, что это как-то может быть связано с организацией наследования - к примеру если есть перегруженная функция init, которая при создании объекта должна вызваться только в конструкторе над-класса и не должна вызваться в конструкторе дочернего класса... но это какие-то пляски с бубном =)
вобщем мне тоже не особо понятно зачем такое.
>>Это и есть главная и единственная его цель в нашем бренном мире... =)
хе. если бы мы взяли АС1, где конструктор над-класса не вызвается сам по себе при создании экземпляра объекта дочернего класса, то сия конструкция вполне могла бы иметь смысл: не переинициализировать экземпляр при вызове super() (это всё конечно опять про перегруженный init =) )
может эти строки в коде АС2 - это пережиток старого шестерочного?
(или я снова чего-то не понял... =) )
пока писал всё предыдущее, подумал: а нафига 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) { ... }
ХА =) оцените это текст:
// Это некоторый класс, использующий загружаемый 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");
угадайте что =))
какие выводы?
зачем нужен целый класс, я лично вообще перестал понимать =))
ай ай =( да простят меня благородные доны, что-то в предыдущем посте с разметкой не заладилось совсем...
Так грузит.Нам бы чо попроще....:(((((((((
На мой взгляд булевые флаги это действительно пережитки AS1 (например, часто применяються в циклах таймлайна, для единственной инициализации переменных или классов).
А насчет реализации события в определенной области видимости, это скорее дело стиля программирования. Можно кодировать линейно, а можно и при помощи классов (что макромедиа и сделала в Delegate, довольно удобно).
выше есть пример, доказывающий что Delegate работает через Function.apply - то есть вчистую вызывает эту же функцию... при таких условиях не понятно зачем целый класс: это примерно то же самое что сделать отдельный класс ради арифметических действий с целыми числами...
Вы абсолютно правы — 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); } }
Но я, всё же, думаю что что с классом таки лучше. И дело не только в том что код короче на две строчки, но в том, что он красивее, структурированный и понятней. Тем более классик то маленький... =)
зы: разметка накрылась потому что не надо пустых строк между строками кода вставлять... ;)
Уважаемый 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 функциональность класса физически создается один раз, а реализации (экземпляры) класса только обращаються к ней как к единой библиотеке. Таким образом мы не засоряем код и делаем приложение компактнее и оптимальнее.
>>Доказывать что Delegate работает через Function.apply собственно и не надо
дада, вы правы конечно =) я не сообразил сразу =)
Мне не дает покоя вопрос, заданный Колином: зачем нужна конструкция типа
Я точно помню, что есть рабочие, связанные со спецификой флэша ситуации (таймлайн), когда именно эта конструкция спасала от повторной инициализации клипа (точнее, класса, связаннного с физическим клипом). Но, как назло, у меня сейчас все в порядке с флэшом, так что я не могу найти пример, когда эта конструкция спасает от ошибок.. но я найду и опубликую здесь. А пока можете просто поверить на слово: данная конструкция -- это не пережиток AS1, а реальная необходимость.
C нетерпением будем ждать... =)
Спасибо! Уже употребил mx.utils.Delegate класс в проекте.
Что бы там не говорили, а пользоватся классом удобнее, чем вызывать напрямую apply и arguments.cellee (и то, и другое недолюбливаю).