« øPor quÈ molesta tanto? | Inicio | [FlashLite] Flash Mobile Community »

Un caso en el que no se deberÌa heredar de movieclip

Sabemos que se puede asignar una clase AS2 a cualquier movieclip que se encuentre en la librerÌa. Si esa clase extiende movieclip, cuando se atachea ( menudo palabro ) el clip se materializa una instancia de esa clase. Pero ni siquiera hace falta hacer el attachmovie. Con colocar el clip en el stage en tiempo de diseÒo es suficiente, la instancia de la clase se materializar· cuando el cabezal llegue a ese frame. Esto puede facilitar el trabajo cuando se est· creando una interfaz de usuario, no hay m·s que colocar el movieclip en el stage y *magia*, se tiene una instancia de la clase asociada.

Imaginemos que hay que crear un interfaz parecido a Èste:

the interface

Podemos escribir una clase como Èsta:

class MyButton extends MovieClip { function MyButton() { stop(); } function onRollOver():Void { gotoAndStop(2); } function onPress():Void { gotoAndStop(3); } function onRollOut():Void { gotoAndStop(1); } }

Si asignamos el valor de la clase de AS2 del movieclip:

linkage.jpg

Entonces, en cuanto esos clips aparezcan en el stage, tendremos dos instancias de la clase MyButton. Muy bien. Hemos encontrado una forma facil te llevar el clip a su segundo frame cuando hagamos rollover, devolverlo al primero al hacer rollout.... Estupendo.

Pero nuestros botones no est·n solos, son parte de una aplicaciÛn. øQuÈ pasa cuando hacemos click en ellos?. øCÛmo interact˙an con el resto de la aplicaciÛn?

Recuerda: no hemos creado las instancias de MyButton a travÈs de su constructor, por lo que no les podemos pasar ning˙n par·metro. Entones, øcÛmo podemos manejar el click del botÛn?

Bueno, podrÌamos hacer que esta clase actuara como emisor de eventos. De este modo, podrÌamos emitir un evento al hacer click en el botÛn.

Supongamos que hay una clase en la que reside la lÛgica de la aplicaciÛn, o que sÛlo es la vista de la misma, da igual. LlamÈmosla AppInstance. Esa clase podrÌa ser instanciada, por ejemplo, en el primer frame del root.

Esa clase no sabe cu·ntos botones hay en el stage, entre otras cosas porque no los ha creado, y por tanto no puede registrarse como listener a los eventos que los botones pudieran emitir

øQuÈ podemos hacer, entonces?. Esos botones son movieclips que est·n colocados directamente en la lÌnea de tiempo principal, asÌ que podrÌamos aÒadir el siguiente mÈtodo a la clase MyButton:

function onRelease( ) { this._parent.doWhatever( ); }

Si quisiÈramos ejecutar una funciÛn llamada doWhatever que estuviera definida en el root, o:

function onRelease( ) { this._parent.appInstance.doWhatever( ); }

Para ejecutar el mÈtodo doWhatever de la clase appInstance.

Pero aquÌ est· el problema. Este cÛdigo huele fatal. øPor quÈ?. Por el _parent. Estamos atacando directamente una lÌnea de tiempo concreta. øQuÈ pasa si tenemos que cargar este swf desde otro?. øQuÈ pasa si la aplicaciÛn no se llama appInstance?. øQuÈ pasa si tenemos que pasar de alguna forma alg˙n indicativo del botÛn que se ha clickeado?. Este botÛn funcionar· sÛlo cuando estÈ colocado en una lÌnea de tiempo en la que haya una variable que se llame appInstance

La verdad, no hay mucha diferencia con la forma en la que se manejaban los botones hace tiempo, cuando se asignaban los handlers "on" directamente en un script asignado al botÛn o al movieclip.

Bueno, y entonces, øquÈ podemos hacer?. °Pues componer, en vez de heredar!.

Vamos a cambiar el cÛdigo de la clase MyButton:

class MyButton { private var timeline: MovieClip; private var buttonId: String; private var btMC: MovieClip function MyButton( timeline: MovieClip, buttonId: String ) { this.timeline = timeline; this.buttonId = butonId; this.btMC = this.timeline.attachMovie( this.buttonId, this.buttonId, this.timeline.getNextHighestDepth( ) ); this.initEvents( ); } private function initEvents( ) { var manager: MyButton = this; this.btMC.onRollOver = function( ) { this.gotoAndStop( 2 ); } this.btMC.onRollOut = function( ) { this.gotoAndStop( 1 ); } this.btMC.onPress = function( ) { this.gotoAndStop( 3 ); } this.btMC.onRelease = function( ) { manager.onRelease( ); } } private function onRelease( ) { } }

He atacheado el movieclip, pero podrÌa haber pasado una referencia al clip como par·metro, algo asÌ como:

function MyButton( btMC: MovieClip, buttonId: String ) { this.buttonId = butonId; this.btMC = btMC; this.initEvents( ); }

Y por tanto, en mi otra clase AppInstance, podrÌa hacer algo asÌ:

var btOK: MyButton = new MyButton( this.btOK, "btOK" ); var btCancel: MyButton = new MyButton( this.btCancel, "btCancel" ); btOK.addEventListener( "click", this ); btCancel.addEventListener( "click", this );

Donde this.btOK y this.btCancel son las referencias a ambos clips. De esta manera puedo seguir colocando los clips en tiempo de diseÒo

Bien, los botones tienen que interactuar con el resto de la aplicaciÛn, asÌ que vamos a hacer que puedan emitir eventos.

Es m·s que probable que no sÛlo estos botones deban emitir eventos, por lo que vamos a encapsular esa funcionalidad en una clase base, de la que luego extender· MyButton

class EventDispatcherImpl { function dispatchEvent() {}; function addEventListener() {}; function removeEventListener() {}; function EventDispatcherImpl() { mx.events.EventDispatcher.initialize(this); } }

Y por tanto, el cÛdigo de MyButton ser·::

class MyButton extends EventDispatcherImpl { private var buttonId: String; private var btMC: MovieClip function MyButton( btMC: MovieClip, buttonId: String ) { this.buttonId = buttonId; this.btMC = btMC; this.initEvents( ); } private function initEvents( ) { var manager: MyButton = this; this.btMC.onRollOver = function( ) { this.gotoAndStop( 2 ); } this.btMC.onRollOut = function( ) { this.gotoAndStop( 1 ); } this.btMC.onPress = function( ) { this.gotoAndStop( 3 ); } this.btMC.onRelease = function( ) { manager.onRelease( ); } } private function onRelease( ) { var eventObject: Object={ target:this, type:'click'} eventObject.buttonId=this.buttonId; dispatchEvent(eventObject); } }

øY si no quiero que emita eventos?. Pues nada, le paso a las instancias de MyButton la referencia de la clase que las construye (serÌa a˙n mejor si le pasara sÛlo su interfaz, pero bueno ):

var btOK: MyButton = new MyButton( this.btOK, "btOK", this );

Ya nos ovlidamos de extender de EventDispatcherImpl. MyButton, por tanto, quedar· asÌ:

class MyButton { private var buttonId: String; private var btMC: MovieClip private var appRef: AppInstance; function MyButton( btMC: MovieClip, buttonId: String, ref: AppInstance ) { this.buttonId = butonId; this.btMC = btMC; this.appRef = ref; this.initEvents( ); } private function initEvents( ) { var manager: MyButton = this; this.btMC.onRollOver = function( ) { this.gotoAndStop( 2 ); } this.btMC.onRollOut = function( ) { this.gotoAndStop( 1 ); } this.btMC.onPress = function( ) { this.gotoAndStop( 3 ); } this.btMC.onRelease = function( ) { manager.onRelease( ); } } private function onRelease( ) { this.appRef.doWhatEver( ); } }

Y ya est·. Hemos hecho el cÛdigo mucho m·s protable, f·cil de mantener, y mucho m·s encapsulado. Hay bastantes soluciones mucho mejores que extender de movieclip y dejar que flash haga el trabajo por nosotros.

TrackBack

URL del Trackback para esta entrada:
http://ctarda.dreamhosters.com/cgi-bin/mt-tb.cgi/664

Comentarios

Toda esta tecnica se basa en el hecho de que no se puede pasar parametros a un constructor de movieclip, pero si se puede:

attachMovie("MyButton","boton",1,{timeline:appInstance})

Al hacerlo por composicion o agregacion en vez de por herencia tambiÈn pierdes:

1- La capacidad de crear un clip compilado con previsualizacion en tiempo de diseÒo. Si tu codigo modifica la apariencia del clip, nunca la veras hasta que se ejecute. Adem·s la compilacion ser· mas rapida. Tampoco podrias modificar las instancias en tiempo de diseÒo a travÈs del inspector de componentes. Tienes que hacerlo todo por codigo y no ver·s nada hasta que se ejecute.

2- El polimorfismo, tu boton jam·s podra ser tratado como un movieclip, como mucho puedes (y en este caso debes) hacer un wrapper para todas las propiedades y metodos. Pero si necesitas usar otra propiedad y/o metodo de lo que deberÌa ser un Movieclip, tienes que hacer un nuevo wrapper. Al final la clase usa composicion/agregacion pero actua como un simple mediador sin ofrecer ninguna ventaja. A mi no me parece que sea mas facil de mantener, todo lo contrario. Si tienes clases que hagan un layout de movieclips, en lo que es por ejemplo un resize de pantalla. Tu clase tampoco valdrÌa, no es un movieclip aunque represente a uno en la pantalla.

3- appInstance debe conocer todo lo que esta instanciado en tiempo de diseÒo. Quizas demasiado, ya que por lo que veo tampoco ser· un Movieclip.

No es que este en contra de la composicion/agregacion de movieclip, en absoluto, hay situaciones en las que es muy ˙til. Pero no creo que un boton sea uno de ellas.

un saludo

Efectivamente, se pueden pasar par·metros, pero ˙nica y exclusivamente cuando hagas un attachmovie.

Si el movieclip est· colocado en el stage, el resto del razonamiento se pierde.

Adem·s, si el movieclip es un botÛn, como en este ejemplo, la ediciÛn del gr·fico es sencillÌsma. No veo la necesidad de crear una previsualizaciÛn en tiempo de diseÒo ( yo no he hablado de componentes, sino de un movieclip que act˙a como botÛn ) si el movieclip est· colocado en el stage. …sa es tu previsualizaciÛn.

En cuanto a que deba hacer un wrapper para todas las propiedades de movieclip, no veo porquÈ. Tan sÛlo tendrÈ que adaptar las propiedades que necesite. Exactamente igual que si fuera un movieclip, caso en el que tampoco iba a necesitar atacar a todas sus propiedades. Adem·s, si no fuera por ese wrapper, no serÌa posible que el movieclip hiciera algo m·s que cambiar su propio estado visual.

En cuanto al polimorfismo, efectivamente, mi clase no es un movieclip. øDÛnde est· el problema?. No lo veo.

En cuanto al conocimiento que tiene appInstance de lo que instancia, tampoco veo el problema. Lo instancia, luego tiene conocimiento de ello. Hay varias colecciones para poder mantener ese conocimiento. Y menos a˙n entiendo el porquÈ tenga que ser un movieclip para poder tener conocimiento de m·s o menos elementos.

Aparte de todo eso, el hacer que la clase que maneja el botÛn extienda de movieclip te impide que Èsta pueda emitir eventos, por ejemplo, y la vincula a la lÌnea de tiempo en la que est· creada. øQuÈ pasa si esa lÌnea de tiempo se carga dentro de otra?. øCÛmo le pasas al botÛn la referencia a la clase con la que tiene que colaborar?. øSigues vinculando una clase a otra y a otra para hacerle llegar esa referencia?

En fin, que no veo ninguna razÛn para instanciar clases a travÈs de un mÈtodo cuya funciÛn es colocar en pantalla gr·ficos. Al contrario, disminuye la portabilidad del cÛdigo y normalmente aumenta el acoplamiento entre entidades ( como en el ejemplo que t˙ propones ).

>Efectivamente, se pueden pasar par·metros, pero ˙nica y exclusivamente cuando hagas un attachmovie.

>Si el movieclip est· colocado en el stage, el resto del razonamiento se pierde.

[Inspectable] en una propiedad permite que le pases parametros en tiempo de diseÒo. Sin contar que appInstance sea probablemente un singleton, con lo que conseguir una referencia a Èl no es demasiado dificil...

> No veo la necesidad de crear una previsualizaciÛn en tiempo de diseÒo

Lo decÌa porque los botones suelen tender a cambiar su apariencia segun su estado y colocacion por el diseÒador.

>En cuanto a que deba hacer un wrapper para todas las propiedades de movieclip, no veo porquÈ.

_x,_y,_width...

>En cuanto al conocimiento que tiene appInstance de lo que instancia, tampoco veo el problema. Lo instancia, luego tiene conocimiento de ello.

appInstance tiene concimiento de lo que hay colocado en pantalla para poder instanciar la version por composicion de MyButton. En un Movieclip es mas logico ya que al fin y al cabo, si estan colocados en pantallas son literalmente propiedades de Èl.

>Aparte de todo eso, el hacer que la clase que maneja el botÛn extienda de movieclip te impide que Èsta pueda emitir eventos

El EventDispatcher funciona igualmente en cualquier clase. De hecho, cuando heredo de MovieClip, lo primero que hago es precisamente que sea un broadcaster de eventos, en vez de utilizar callbacks. Solo que utilizo "implements IEventDispatcher" en vez de "extends EventDispatcherImpl". Heredar para eso tiene menos logica que hacerlo de Movieclip.

Hola de nuevo, Joseba ( por cierto, øeres Joseba Alonso el de 5dms.com? ).

Me temo que hablamos lenguajes totalmente diferentes. De todas formas, ya sÈ cu·les son las propiedades de MovieClip, y que cualquier clase se puede inicializar como emisora de eventos.

Eso sÌ, si est·s inicializando todos tus emisores de eventos uno por uno, algo hay que est·s haciendo mal, porque para eso sÌ que est· la herencia. Para eso, por tanto, sÌ es para lo que es lÛgico heredar de una clase base que implemente la funcionalidad com˙n a otras ( cosa, que evidentemente, el movieclip no hace ).

En cuanto a que appInstance sea o no un singleton, pues no sÈ de dÛnde lo sacas, la verdad.

Sobre el cambio de apariencia de los botones, pues sÌ, ya sÈ que los diseÒadores suelen tender a hacerlos como les gusta a ellos, a cambiarlos, retocarlos, etc. Cosa que no se impide al no heredar de movieclip.

appInstance no instancia MovieClips y no maneja MovieClips, crea instancias de la clase MyButton. De hecho, si nos ponemos puntillosos, es asÌ como deberÌa ser, porque la lÛgica de la aplicaciÛn no deberÌa tener nunca conocimiento de sus elementos gr·ficos. Los botones, por tanto, no son propiedad del modelo, sino que son manejados por un controlador ( en este caso la clase MyButton ). Esa indirecciÛn, precisamente, es la que permita que la aplicaciÛn sea m·s modular y por tanto m·s escalable y f·cil de mantener, al contrario de lo que pasarÌa en el caso de vincular directamente los gr·ficos al modelo.

Sobre las propiedades de posiciÛn y tamaÒo del clip: si el clip est· colocado en tiempo de diseÒo, no son necesarias.

Y sobre el uso de [Inspectable], la verdad, si ya me parece raro instanciar una clase a travÈs de un mÈtodo que sirve para colocar gr·ficos en el stage ( attachmovie ), m·s raro a˙n me parece el pasarle par·metros a travÈs de un panel que sÛlo aparece en tiempo de diseÒo. Eso no creo que ayude mucho a contruir una aplicaciÛn particularmente din·mica. Ni a que tu clase que extiende de movieclip se pueda utilizar en otras aplicaciones que no sean exactamente igual a Èsta.

Que, por cierto, es una de las motivaciones fundamentales para la programaciÛn orientada a objetos.

Echaba de menos estas disertaciones de herencia vs composiciÛn.

La verdad es que me entero de la misa la mitad pero albergo la esperanza de un dia darme un golpe en la cabeza y que, como una epifanÌa religiosa, todo cobre sentido.

Hablando en serio, es muy ˙til conocer los motivos de gente que se basa en la experiencia a la hora de estructurar una aplicaciÛn.

elSuricatoRojo

Publicar un comentario

(Si no dejó aquí ningún comentario anteriormente, quizás necesite aprobación por parte del dueño del sitio, antes de que el comentario aparezca. Hasta entonces, no se mostrará en la entrada. Gracias por su paciencia).