« ¿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.

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

Hola Cesar. Utilizando el principio de tu composición, hice algunas reformulaciones que me gustaría compartir. Puedes contactarme a info@zamoradesign.com.ar.

Saludos


sebazam