Lo que yo sé de AS2 #Comunicación entre clases
Lo que yo sé de AS2 (condensado y pasteurizado)
*AUTOR: Juan Delgado Zárate
zarate.tv | dandolachapa.com | loqueyosede.com
FlashLa.com | After-Hours.org
Comunicación entre clases
Una de las partes más importantes y que más confusión y problemas ha generado de AS2 es la comunicación entre clases. Foros y listas de correo están llenos de problemas y dudas sobre “ámbitos”, “eventdispatchers” y “delegates”.
Como regla general yo diría:
- Si necesitas comunicación dentro de la misma clase, siempre Delegate.
- Si necesitas que más de una clase escuche lo que hace otra, siempre EventDispatcher.
- Si la comunicación es 1:1, depende.
Vamos a ver el funcionamiento de los 2 métodos.
Delegate
NOTA: Lo que voy a contar aquí es prácticamente un copy&paste de un tuto que publiqué hace tiempo y que está enlazado al final. Si lo leíste en su día, te puedes saltar lo que sigue sin problemas.
Los foros de Flash están llenos de preguntas que tienen que ver con el “scope” o ámbito dentro de los famosos “callbacks” de objetos como XML, LoadVars, XMLSocket, etc. Flash necesita estas funciones porque las llamadas al servidor en Flash son asíncronas, es decir, el player no detiene la ejecución del código cuando se hace, por ejemplo, una petición de un fichero xml:
- classA{
- private var oneVar:String="Helloworld";
- functionA(){
- varmyXML:XML = newXML();
- myXML.ignoreWhite = true;
- myXML.onLoad=function():Void{
- trace(oneVar); //undefined ¬¬
- }
- myXML.load("myXML.xml");
- }
- }
Esto pasa porque el ámbito “dentro” del onLoad es el propio objeto myXML, NO la clase. Puedes hacer la prueba haciendo trace(this) dentro del onLoad. Una de las primeras soluciones que se utilizó para esto fue crear dentro de la función principal una
variable que hacía referencia a la propia clase. Algo como esto:
- classA{
- private var oneVar:String="Helloworld";
- functionA(){
- var owner=this;
- var myXML:XML = newXML();
- myXML.ignoreWhite = true;
- myXML.onLoad = function():Void{
- trace(owner.oneVar); //yeah!
- }
- myXML.load("myXML.xml");
- }
- }
Y esto funciona. Este comportamiento “peculiar” de Flash (de los ECMAScript, vamos) se llama closure.
ámbito en las llamadas asíncronas. Pues a todo esto llegó la versión 7.2 del Flash IDE y el señor Mike Chambers introdujo la clase Delegate. Utilizando esa clase dejaríamos el código anterior en algo como esto:
- importmx.utils.Delegate;
- classA{
- private var oneVar:String = "Hello world 2";
- function A(){
- var myXML:XML = new XML();
- myXML.ignoreWhite = true;
- myXML.onLoad = Delegate.create(this,xmlLoaded);
- myXML.load("myXML.xml");
- }
- private function xmlLoaded(success:Boolean):Void{
- trace(oneVar);
- }
- }
Estamos “delegando” el onLoad en la función xmlLoaded, pero, lo más importante, el ámbito de la función xmlLoaded es la clase original, por lo que “encontramos” la variable sin problemas.
Esto definitivamente NO es lo mismo que hacer: myXML.onLoad = xmlLoaded. Si lo probáis, estaréis con el mismo problema que antes, el ámbito de la función xmlLoaded será el objeto myXML, por lo que el trace volverá a ser undefined.
El mayor problema de la clase de Macromedia (aún era Macromedia) es que NO permite el paso de parámetros a la función delegada, pero pronto llegaron los frikis para solucionarlo haciendo sus propias clases para delegar. La que yo utilizo es una copia con alguna modificación de una que encontré en la lista de MTASC. Con estas nuevas clases se pueden pasar parámetros de la siguiente forma:
- myXML.onLoad=Delegate.create(this,xmlLoaded,"val1",val2);
OJO, nuestros parámetros llegarán después de los “oficiales”, en este caso el típico success que llega a los onLoad del objeto XML.
EvenDispatcher
La misión básica de EvenDispatcher es permitir que varios objetos estén “escuchando” los eventos que lanza otro. EvenDispatcher siempre trata de comunicación entre clases, ya sea 1:1 (un emisor y un receptor) o 1:n (un emisor y varios receptores).
Técnicamente es una implementación del patrón Observer (ver enlaces al final) y fue introducida en su día por Macromedia. Pero como casi siempre apareció una versión libre, GDispatcher, en esta ocasión a cargo de Grant Skinner (gskinner.com).
Las clases que emiten eventos con EvenDispatcher son completamente independientes de quien esté escuchando, nunca saben si los escuchan 1 o 10, ellos sólo se limitan a emitir. Son como una radio o televisión con audiencia.
Sólo el objeto emisor necesita importar GDispatcher. Algo como lo siguiente:
- importcom.gskinner.GDispatcher;
- classbroadcaster{
- public var addEventListener:Function;
- public var removeEventListener:Function;
- private var dispatchEvent:Function;
- public function broadcaster(){
- GDispatcher.initialize(this);
- send();
- }
- private function send():Void{
- var ev:Object = new Object();
- ev.type = "myEvent";
- ev.variable = "wadus";
- dispatchEvent(ev);
- }
- }
- import broadcaster;
- class receiver{
- public function receiver(){
- varbr:broadcaster = newbroadcaster();
- br.addEventListener("myEvent",this,"callback");
- }
- private function callback(ev:Object):Void{
- trace(ev.type+""+
- ev.variable);
- }
- }
Lo primero que llama la atención es que hayamos definido en la clase emisora unas variables del tipo Function. Si alguien se molesta en abrir GDispatcher puede ver que lo que hace en el método initialize es crear en tiempo de ejecución funciones a la clase que se le pasa como parámetro. Así que tenemos que definir esas mismas funciones como propiedades de nuestra clase para que el compilador no se queje de que el método al que el receptor trata de acceder (addEventListener) no existe.
La clase receptora es bastante simple. Simplemente importa a la emisora y se añade como listener para un evento concreto. Para hacerlo utiliza el método addEventListener pasando como parámetros el evento que quiere escuchar, el objeto que está escuchando (la propia clase (this) en este caso, pero se puede hacer que escuche otra) y la función que va a recibir la llamada. OJO que la función está pasada como cadena.
La implementación que hemos visto es perfectamente válida y funcional pero se puede mejorar. El primer problema es que el literal del nombre del evento (“myEvent”) lo estamos repitiendo dos veces, una en el emisor y otra en el receptor. Malo. Otra es que el tipo del parámetro que llega a la función receptora es del tipo genérico Object, lo que no nos permite ninguna validación de tipos al compilar. Malo también. Bueno, pues las 2 cosas las vamos a solucionar en la misma jugada. Vamos a crear un objeto específico para el evento y será ese objeto “conocido” el que se envíe.
- classmyEvent{
- public static var EVENT_LITERAL:String = "eventOne";
- public var type:String = "";
- public var variable:Number;
- public function myEvent(_variable:Number){
- type = EVENT_LITERAL;
- variable = _variable;
- }
- }
- importcom.gskinner.GDispatcher;
- import myEvent;
- class broadcaster{
- public var addEventListener:Function;
- public var removeEventListener:Function;
- private var dispatchEvent:Function;
- public function broadcaster(){
- GDispatcher.initialize(this);
- send();
- }
- private function send():Void{
- var ev:myEvent = new myEvent(5);
- dispatchEvent(ev);
- }
- }
- import broadcaster;
- import myEvent;
- class receiver{
- public function receiver(){
- var br:broadcaster = new broadcaster();
- br.addEventListener(preventivamente_LITERAL,this, "callback");
- }
- private function callback(ev:myEvent):Void{
- trace(ev.type + " "
- + ev.variable);
- }
- }
Mucho mejor. El literal con el nombre del evento sólo se define una vez en la clase myEvent. Se define como variable estática para que la clase receptora pueda utilizarlo sin tener que crear una instancia. La propiedad type se define para ser compatibles con EventDispatcher y GDispatcher.
Además con la nueva implementación el tipo del evento que llega al receptor es myEvent y no Object, por lo que tendremos validación de tipos al compilar.
Si sustituyes myEvent, por videoStarted, applicationReady, menuButtonPressed y similares, es cuando se empieza a ver la utilidad de EvenDispatcher (vamos, de un patrón Observer).
EvenDispatcher ¿vs? Delegate
Una vez vistos EventDispatcher y Delegate, ¿hay situaciones en las que se pueda elegir entre usar una opción o la otra? Ya hemos visto que la comunicación dentro de la misma clase siempre es con Delegate, así la decisión viene cuando hay que comunicar 2 clases. Vamos a ver cómo.
- importBroadcaster;
- importtv.zarate.utils.Delegate;
- class Receiver{
- public function Receiver(){
- var buttonCallback:Function = Delegate.create(this, buttonPressed);
- var broadcaster:Broadcaster = newBroadcaster(buttonCallback);
- }
- private function buttonPressed():Void{
- //este método se ejecuta cuando se presione el botón
- //en la clase emisora
- }
- }
- class Broadcaster{
- public function Broadcaster(buttonPressedCallback:Function){
- var button:MovieClip = base_mc.createEmptyMovieClip("button", 100);
- button.onPress = buttonPressedCallback;
- }
- }
Atención a la jugada. La clase receptora crea una función delegada y la pasa como parámetro a la emisora que simplemente se lo asigna al evento onPress de su botón. Así que cuando el botón es presionado, la clase emisora recibe sin problemas el evento.
Ahora, ¿qué hacemos si hay que pasar parámetros en la llamada? Esto es algo que NO podríamos hacer:
- button.onPress = buttonPressedCallback("parametro");
Ya que al añadir “()” estaríamos ejecutando la función. Si necesitamos pasar parámetros con este método, habría que pasar por una función intermedia. Repito sólo la clase emisora:
- importtv.zarate.utils.Delegate;
- class Broadcaster{
- public function Broadcaster(externalCallback:Function){
- var button:MovieClip = base_mc.createEmptyMovieClip("button", 100);
- button.onPress = Delegate.create(this, butPressed, externalCallback);
- }
- private function butPressed(externalCallback:Function):Void{
- externalCallback("parametro");
- }
- }
Al utilizar la clase emisora internamente una función para recibir el evento onPress del botón, ya puede pasar los parámetros que quiera al callback externo.
[ anterior ] [ siguiente ]
Compártelo:
Visto 7.274 veces
[…] Comunicación entre clases […]
Hola, muy bueno el articulo. Pero por ejemplo en este ultimo caso. dices que al recibir el evento onPress del boton ya puede pasar los parámetros que quiera al callback.
Como pasarÃa por ejemplo una referencia del botón presionado??
otra pregunta.
Pq usar EvenDispatcher / Delegate en vez de l patrón observer? aporta algún beneficio?
gracias
Despues de muxo pegarme con tu código Zarate, he llegado a una solucion que me gustaria plantaerte en el ejemplo básico de EvenDispatcher, que solo funciona si lo metes en un intercvalo, y despues de cmbiar el flujo de la programación, he cambiado la llamada a la funcion send(), dentro de la clase broadcaster, despues de crear el listener, no se si es la forma mas correcta pero he visto en la url:
url
que se hacia de esta manera y el flujo de la informacion es la correcta pues recibo el objeto ev, espero que esto ayude a alguien y si esta mal planteado tb espero recibir las criticas, cualqquier critica estoy aprendiendo.
GRACIAS