« øEn defensa de la familia? | Inicio | Google earth gratuito »

No te repitas, y encapsula lo que pueda cambiar

No te repitas. En cuanto escribes el mismo cÛdigo m·s de una vez, est·s empezando a meterte en problemas

AquÌ tienes un ejemplo.

Estoy trabajando en una aplicaciÛn en la que voy a utilizar los componentes de UI. Es una aplicaciÛn Modelo-Vista-Controlador, por lo que la vista estar· totalmente desacoplada de la lÛgica de la aplicaciÛn

La aplicaciÛn va a tener m·s de una vista ( hay varias "pantallas" diferentes en las que tengo que mostrar los datos de diferentes formas ), por lo que he decidido que lo mejor ser· escribir una clase diferente para cada una de esas vistas.

Por tanto, cada vista attachear· un movieclip, que es el que contiene realmente la "pantalla" con los elementos interactivos ( botones, datagrid, etc ). Todas esas vistas extienden de una clase que implementa cierta funcionalidad com˙n ( emisiÛn de eventos y alguna cosilla m·s ). Por tanto, no extender·n de movieclip ( aparte de por razones un poco m·s abstractas, aunque Èste no es el momento para discutir eso ). A lo que vamos.

Adem·s, resulta ( todo son facilidades ) que por ahora no tengo los gr·ficos definitivos ( vamos, que me lo estoy pintando yo ). A˙n m·s, si al final el presupuesto da para ello, tendrÈ que desarrollar mis propios componentes.

Bien, pues una vez puestos en antecedentes, imagina que arranca la aplicaciÛn. Lo primero con lo que se va a encontrar el usuario es con una pantalla de login

encapsulate.jpg

Como puedes ver en la captura, a) m·s me vale que me vaya bien en esto de la programaciÛn, por que lo que es en la parte gr·fica...; b) la pantalla contiene un componente ( el botÛn ).

La clase que maneja esa pantalla ser·:

import mx.utils.Delegate; class LoginView { private var timeline : MovieClip; private var loginScreen: MovieClip; function LoginView( tl: MovieClip ) { this.timeline = tl; } public function init( ) { this.loginScreen = this.timeline.attachMovie( "LoginScreen", "LoginScreen", this.timeline.getNextHighestDepth( ), { _x: 0, _y: 0 } ); this.loginScreen.loginBtn.label= "Go!"; this.loginScreen.loginBtn.addEventListener( "click", Delegate.create( this, click ) ); } private function click( ) { trace( "click" ); } }

Y cuando quiera que aparezca, harÈ algo como:

var loginView: LoginView = new LoginView( this ); loginView.init( );

Y el botÛn no va a funcionar. Ni siquiera va a mostrar el texto que le he asignado. Tras unos minutos de buscar en google, un poco de sentido com˙n, y un poco menos de conocimiento de la plataforma, he llegado a la conclusiÛn de que el botÛn no se inicializa a tiempo, y que tal vez deberÌa dejar pasar un frame para asignar el texto y el click.

import mx.utils.Delegate; class View { private var timeline : MovieClip; private var loginScreen: MovieClip; function View( tl: MovieClip ) { this.timeline = tl; } public function init( ) { this.loginScreen = this.timeline.attachMovie( "LoginScreen", "LoginScreen", this.timeline.getNextHighestDepth( ), { _x: 0, _y: 0 } ); var theView: View = this; this.loginScreen.onEnterFrame = function( ) { theView.initButton( ); delete this.onEnterFrame; } } private function initButton( ) { this.loginScreen.loginBtn.label= "Go!"; this.loginScreen.loginBtn.addEventListener( "click", Delegate.create( this, click ) ); } private function click( ) { trace( "click" ); } }

Y funciona. Pero esta soluciÛn no huele demasiado bien ( y no sÛlo por el truqui del scope ). Voy a tener que hacer bastantes pantallas ( alrededor de una docena ), asÌ que voy a tener que repetir ese cÛdigo en todas ellas. Desde luego, podrÌa subclasificar, pasar ese cÛdigo a una clase base, pero no voy a tener el mismo n˙mero de botones en todas las pantallas, luego tampoco tendrÌa mucho sentido subclasificar para luegosobreescribir, y adem·s, sÛlo podrÌa utilizar ese cÛdigo en subclases de la clase base que creara, con lo cual no iba a tener demasiada flexibilidad, que digamos. Y adem·s, mis vistas ya extienden de otra clase base.

E incluso puede ser peor, porque es probable que tenga que cambiar esos componentes por unos mÌos ( o no, que a˙n no lo sÈ ). AsÌ que es m·s que probable que ese cÛdigo tenga que cambiar en un futuro no muy lejano

AsÌ pues, estoy repitiendo cÛdigo, y adem·s, es probable que ese cÛdigo tenga que cambiarlo en breve. øCÛmo lo puedo resolver?. Pues encapsulando ese cÛdigo dentro de su propia clase.

Para empezar, usarÈ un callback que ya he utilizado m·s veces:

class Callback { private var callbackObjVal: Object; private var callbackMethodVal: String; public function Callback( objParam: Object, methodParam: String ) { this.callbackObjVal = objParam; this.callbackMethodVal = methodParam; } public function fire( parameter: Object ): Object { return this.callbackObjVal[ this.callbackMethodVal ]( parameter ); } }

La clase encargada de manejar el botÛn ser·:

class ButtonHandler { public static function initButton( mc: MovieClip, callback: Callback ) { var cb: Callback = callback; mc.onEnterFrame = function( ) { cb.fire( ); delete this.onEnterFrame; } } }

Y la utilizarÈ de la siguiente forma:

import mx.utils.Delegate; class View { private var timeline : MovieClip; private var loginScreen: MovieClip; function View( tl: MovieClip ) { this.timeline = tl; } public function init( ) { this.loginScreen = this.timeline.attachMovie( "LoginScreen", "LoginScreen", this.timeline.getNextHighestDepth( ), { _x: 0, _y: 0 } ); ButtonHandler.initButton( this.loginScreen, new Callback( this, "initButton" ) ); } private function initButton( ) { this.loginScreen.loginBtn.label= "Go!"; this.loginScreen.loginBtn.addEventListener( "click", Delegate.create( this, click ) ); } private function click( ) { trace( "click" ); } }

øUna soluciÛn excesivamente compleja?. Puede, pero es la que m·s me ha satisfecho, porque es la que aporta m·s flexibilidad. Si tengo que cambiar la forma en la que manejo los botones, sÛlo tengo que cambiar la implementaciÛn de un mÈtodo en una clase. Estoy programando pensando en el interfaz, y no en la implementaciÛn.

TrackBack

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

Comentarios

Efectivamente, tanto attachMovie() como loadMovie() son mÈtodos asÌncronos.

tambiÈn lo podÌas haber solucionado m·s sencillo asi, :

public function init( )
{
this.loginScreen = this.timeline.attachMovie( "LoginScreen", ...);


this.time.onLoad = function(){
this.loginScreen.loginBtn.label= "Go!";
this.loginScreen.loginBtn.addEventListener( "click", Delegate.create( this, click ) );
}

}

Perdona, me he colado mandandote el comentario anterior varias veces.

Pensando sobre loadmovie() y attachmovie() como asincronos, me viene una duda:

Si un MovieClip aÒadido con AttachMovie() crea a su vez otros MC¥s dentro de el con el mismo metodo AttachMovie(), el evento onLoad del primero se lanza cuando todos los hijos han lanzado recursivamente un evento onLoad()?

Si no fuese asÌ podrÌa haber 'hijos de los hijos' no inicializados en el momento que se dispara onLoad().

øQue opinas de esto?

Saludos

Pues la verdad, no sÈ muy bien lo que pasarÌa cuando haya m·s clips dentro del primer clip, pero todo es probar.

En cuanto a la soluciÛn de onLoad, efectivamente, es posible, pero no es muy distinta a la de dejar pasar un frame, porque habrÌa que seguir copiando y pegando ese cÛdigo en varias clases, y el manejo del botÛn seguirÌa sin estar encapsulado, y sin ser muy f·cil de cambiar.

En todo caso, es una soluciÛn al problema de la inicializaciÛn del componente que no se me habÌa ocurrido probar. Gracias!

Hola Cesar,

Solo querÌa aportar mi granito de arena recordando que si se usa el framework v2 de Macromedia (...aunque ya se que est·s pensando en una situaciÛn generica, pero en caso de que uses finalmente v2), existe el mÈtodo doLater, que permite ejecutar cÛdigo una vez que la interface se ha actualizado.

Saludos :)

En lugar de hacer un onEnterFrame, que funciona, puedes usar doLater para inicializar los componentes. Al mÈtodo init de cada vista, que por lo que pones deber·s llamarla cada vez que crees una vista, podrÌas pasarle los datos para inicializar los componentes, y emplear el mÈtodo doLater en dicha funciÛn de cada vista.

Se me ha olvidado un artÌculo sobre eso mismo: http://www.markme.com/pent/archives/007731.cfm
;)

Efectivamente, se puede utilizar el doLater para saber cu·ndo se ha inicializado el botÛn, pero a estas alturas, no estoy seguro de si el botÛn va a ser uno de los componentes del framework V2 o va a tener que ser mi propio componente, es decir, es probable que la forma de manejar esa inicializaciÛn vaya a cambiar.

De ahÌ la idea de encapsular esa inicializaciÛn en su propia clase...

La idea del post es que si hay algo que preveemos que puede cambiar, hay que intentar no repetirlo (y en general hay que intentar no repetir cÛdigo ), y a ser posible, encapsularlo en una clase para que se pueda modificar sin afectar al resto del programa.

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).