Lo que yo sé de AS2 #Unos apuntes
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
Unos apuntes
Aquí van unos comentarios que no sé dónde encajar pero que me parecen útiles a la hora de pensar aplicaciones AS2.
¿Extender o interface?
Para tomar esta decisión hay varias cosas a tener en cuenta. Por ejemplo, en Flash no hay herencia múltiple (sólo se puede extender de una clase), pero se pueden implementar varias interfaces.
Sin embargo, para mi la clave viene dada por cuántas cosas en común van a hacer las distintas implementaciones. Por ejemplo es normal que un grupo de vistas tengan un montón de código común:
- Tener un método setSize
- Tener una instancia del modelo
- Tener una variable base_mc, con una referencia al MovieClip base sobre el que trabajar.
- Un método config() con parámetros comunes
- …
Parece que hay bastante código común, así que se podría guardar todo ese código común en una clase CommonView y luego hacer que las vistas específicas la extiendan y sobrescriban sólo los métodos necesarios.
Sin embargo, el ejemplo puesto en el apartado del patrón Factory en el que bajo la misma interface se implementaban 2 contenedores distintos que proporcionan acceso al sistema de archivos para la misma aplicación, el código común entre la clase de Screenweaver y la clase para lenguaje de servidor es mínimo, por lo que la interface es mejor.
¿Extender de MovieClip? No, gracias
Éste es un tema sobre el que hay bastante “polémica” en cualquier lista de correo de Flash. Así que volveré a dejar por escrito que ésta es _mi_ forma preferida de hacerlo. Sin embargo, no es un capricho, éstas son mis razones:
- En Flash no hay herencia múltiple, así que si extiendes de MovieClip no puedes extender de nada más.
- Las clases que extienden de MovieClip no se instancian mediante new, sino mediante attachMovie, lo que es bastante raro desde el punto de vista de un programador.
Cuando creo clases que tienen un componente gráfico (un menú, un TextArea, un player de vídeo) mi forma de trabajar es pasar a las clases un MovieClip base sobre el que “trabajan” (utilizar composición en lugar de herencia). Algo como:
- classvideoplayer{
- privatevarbase_mc:MovieClip;
- publicfunctionvideoplayer(_base_mc:MovieClip){
- base_mc=_base_mc;
- }
- }
A la hora de instanciar la clase:
- varvp_mc:MovieClip=timeLine_mc.createEmptyMovieClip("vp_mc",100);
- varvp:videoplayer=newvideoplayer(vp_mc);
Normalmente dejo a la clase que instancia el objeto videoplayer que decida cosas como la posición x,y del MovieClip. Esto lo hago así ya que considero que la única responsabilidad del videoplayer es poner un vídeo “donde le manden”, no decidir cual es la posición dentro de la pantalla, por ejemplo. Aún así, esto es una regla general que muchas veces depende de la organización de clases y objetos que tenga una aplicación en concreto.
Hasta hace poco estaba en un error que Joseba Alonso (sidedev.net) me aclaró amablemente. Las clases que heredan de MovieClip NO heredan su dinamicidad. MovieClip es una clase dinámica (se le pueden crear propiedades y métodos en tiempo de ejecución), pero las clases que extienden de ella no tienen por qué serlo.
El problema (ventaja al mismo tiempo en algunas ocasiones) de las clases dinámicas es que se le pueden crear propiedades y métodos en tiempo de ejecución. El problema de eso es que si te equivocas escribiendo el nombre de una propiedad, el compilador no lanza error ya que sobreentiende que estás creando una propiedad nueva. Ejemplo:
- dynamicclassmiClaseDinamica{
- publicvarvariable:String="Helloworld";
- publicfunctionmiClaseDinamica(){}
- }
A la hora de usarla:
- varinstancia:miClaseDinamica=newmiClaseDinamica();
- varvalor:String=instancia.avriable;//elcompiladorNOdaerror
A pesar del error (usar “avriable” en lugar de “variable”), el compilador asume que se está accediendo a una variable que se ha creado en tiempo de ejecución, cuando lo más normal es que queramos acceder a la propiedad “variable”.
Pero esto al mismo tiempo es una ventaja. ¿Cuántas veces habremos creado propiedades a un MovieClip en tiempo de ejecución? Algo como:
- for(varx:Number=0;x<10;x++){
- varboton:MovieClip=timeLine_mc.createEmptyMovieClip("boton_"+x,100+x);
- boton.opcion=x;
- }[/as]</div>
- Aunque útil, muchas veces es mejor crear una pequeña clase para el manejo del botón. Esa clase podría tener una propiedad pública "opcion" sobre la que estableceríamos el valor.
- <h2>Objetos responsables</h2>
- Uno de los principios OOP es que cada objeto haga única y exclusivamente lo que tiene que hacer. Cuando una clase sabe demasiado o hace demasiado, se le conoce como la clase Dios o God Object (ver enlace al final).
- Sin llegar a límites absurdos, casi siempre es mejor trabajar con muchas clases pequeñas, cortas y concisas que con clases de 1.000 líneas. Que cada una haga su pequeño trabajito y poco más. Esto claramente favorece la reutilización y la depuración de errores (el código a revisar por clase es menor).
- Supongamos que tenemos una clase para pintar un player de vídeo. Aquí van 2 ejemplos de implementación:
- <div id=codigo>[as]importVideoPlayer;
- classA{
- publicfunctionA(){
- varvp:VideoPlayer=newVideoPlayer(timeLine_mc);
- }
- }
- classVideoPlayer{
- privatevarbase_mc:MovieClip;
- publicfunctionVideoPlayer(timeLine_mc:MovieClip){
- base_mc=timeLine_mc.createEmptyMovieClip("base_mc",100);
- base_mc._x=10;
- base_mc._y=10;
- //ahoraempezaríamosapintarelpropioplayer
- }
- }
En el código anterior, la clase VideoPlayer se está encargando de crearse su propio clip sobre el que trabajar y de colocarlo en la posición deseada. Sin embargo la siguiente implementación es más flexible:
- importVideoPlayer;
- classA{
- publicfunctionA(){
- video_mc=timeLine_mc.createEmptyMovieClip("video_mc",100);
- video_mc._x=10;
- video_mc._y=10;
- varvp:VideoPlayer=newVideoPlayer(video_mc);
- }
- }
- classVideoPlayer{
- privatevarbase_mc:MovieClip;
- publicfunctionVideoPlayer(_base_mc:MovieClip){
- base_mc=_base_mc;
- //ahoraempezaríamosapintarelplayer
- }
- }
En la segunda versión la clase VideoPlayer no tiene la responsabilidad de crear su MovieClip de trabajo ni de colocarlo en la pantalla. ¿Por qué debería hacerlo si no lo necesita? Que sea la clase que lo instancia quien tenga esa responsabilidad. Así cuando haya que reutilizar la clase de VideoPlayer, no hay que hacerle ninguna modificación.
Dicho esto, el astuto lector estará pensando que la clase VideoPlayer podría recibir como parámetros la posición x,y donde se tiene que pintar, el nombre del MovieClip y la profundidad a la que va…. sí, técnicamente es posible, pero para qué pasar un montón de parámetros cuando se puede no pasar ninguno : )
Mejor que no te acoples
Una de las normas consideradas como correctas dentro de la programación orientada a objetos es que las clases entre sí deben estar poco “acopladas”. Eso significa que cuanto menos sepa una clase de otra, mejor. Siempre que sea posible es más flexible que las clases trabajen sobre parámetros en lugar de hacerlo sobre otros objetos. Ejemplo:
- importB;
- classA{
- publicvarvariable:String="Helloworld",
- publicfunctionA()
- {
- vara:A=newA(this);
- }
- }
- importA;
- classB{
- public function B(a:A){
- trace(a.variable)
- }
- }
Según la estructura de código de arriba, la clase B necesita importar la clase A para acceder a su propiedad pública cuando realmente no es necesario. Modificando el código como sigue, las clases no están acopladas:
- importB;
- classA{
- privatevarvariable:String="Helloworld",
- publicfunctionA(){
- vara:A=newA(variable);
- }
- }
- classB{
- publicfunctionB(toTrace:String){
- trace(toTrace);
- }
- }
El resultado es el mismo, pero en el segundo caso la clase B no necesita a la clase A para nada. Si por necesidades del proyecto necesitamos que sea la clase C en lugar de la A la que inicie el proceso, a la clase B le va a dar igual porque lo único que necesita es una cadena en el constructor.
Como toda regla general, depende mucho del proyecto decidir si pasar una instancia de una clase o un montón de parámetros.
Protos Nunca Mais
Una de las preguntas más habituales para la gente que está dando el paso de AS1 a AS2 es cómo reaprovechar protos hechos en AS1 en AS2. Para mi la salida más natural a los protos son clases con un método estático para cada proto.
Ejemplo de proto sacado de Layer51
- String.prototype.PALINDROME=function(){
- returnthis+this.split("").reverse().join("");
- };
- classtv.zarate.Utils.StringUtils{
- publicstaticmethodpalindrome(s:String):String{
- returns+s.split("").reverse().join("");
- }
- }
A partir de ese momento, las llamadas serían así:
- importtv.zarate.Utils.StringUtils;
- trace(StringUtils.palindrome("wadus"));
Es importante fijarse en que para utilizar el método estático no hace falta crear una instancia de la clase StringUtils;
Como en los métodos estáticos no se puede hacer la referencia a “this” que se hacía en el proto, se pasa como parámetro el objeto (en este caso una cadena) con el que el método tiene que “trabajar”. Por supuesto si hubiera que trabajar con más parámetros, se pasarían a continuación.
¿Cuándo se compila una clase?
Algo que no está meridianamente claro es cuándo el compilador incluye una clase en un swf. La respuesta es cuando esa clase está referenciada *en el código*. Y resalto lo de “en el código” porque si la clase está importada pero no usada, no se compila. Ejemplo:
- importclassB;
- classA{
- publicfunctionA(){}
- //laclaseBnosecompila
- }
- importclassB;
- class A{
- public function A(){
- var b:B = new B();
- }
- //laclaseBsecompila
- }
- importclassB;
- classA{
- privatevarb:B;//AQUITAMBIENSECOMPILA!
- publicfunctionA(){
- }
- }
Otra de las ventajas de utilizar MTASC es que por defecto avisa con un warning cuando se están importando clases que luego no son utilizadas dentro del código.
¿Y se puede evitar que una clase se compile incluso cuando se ha referenciado en el código? Claro que sí, aunque la forma de hacerlo no sea muy “amigable”. Se trata de utilizar archivos *_exclude.xml, pero como el tema es un poco largo, en la sección de Enlaces dejo recursos sobre el tema.
Importando packages
Algo muy habitual es importar un package completo utilizando “*”:
- importcom.xfactorstudio.xml.xpath.*;
¡OJO! El compilador NO incluye todas las clases del package, ya que hace su trabajo de optimización y sólo compila las que están referenciadas en el código.
¿Entonces por qué no se importa siempre todo el package? Yo diría que es una cuestión didáctica. Prefiero importar explícitamente sólo las clases que voy a utilizar y así poder ver rápidamente de un vistazo las relaciones entre clases.
Getters y setters implícitos y explícitos
Se conocen por getters/setters a los métodos que permiten establecer y recuperar el valor de una variable. La idea es que la variable en si sea privada, y que para que acceder a ella haya que llamar a estos métodos. ¿Y por qué querría nadie obligar a usar un método para saber el valor de una variable? Pues porque hay veces que al cambiar el valor de una variable, otras variables
relacionadas también cambian su valor. Por ejemplo:
- classA{
- privatevarprice:Number;
- publicfunctionA(){
- }
- publicfunctiongetPrice():Number{
- returnprice;
- }
- publicfunctionsetPrice(p:Number):Void{
- price=p;
- updateBasketPrice();//acciónrelacionada
- }
- }
Estos son los getters/setters explícitos. Y son explícitos porque para saber el valor de la variable desde fuera de la clase, hay que llamar a un método.
Pero Flash permite hacerlos de forma implícita. Lo siguiente es perfectamente válido:
- classA{
- privatevar_price:Number;
- publicfunctionA(){}
- publicfunctiongetprice():Number{
- return_price;
- }
- publicfunctionsetprice(p:Number):Void{
- _price=p;
- updateBasketPrice();//accionrelacionada
- }
- }
¿Ventajas de un método sobre otro? Técnicamente yo diría que no hay, pero hay programadores que prefieren hacer saber que se accede a un método. Otros dicen que no tiene por qué dar explicaciones. A gusto del consumidor.
Claro, que también hay gente que piensa directamente que los getters y setters deberían desaparecer de la faz de la tierra. En la sección de Enlaces hay más sobre ello para quien le interese.
Strong data typing
Strong data typing es de esos términos que casi mejor no traducir. Pero vamos, significa que le asignes un tipo a todas las propiedades, parámetros y tipos devueltos por los métodos. Aunque no es obligatorio, yo creo que es muy recomendable hacerlo.
- varmyString:String="";
- varmyNumber:Number=0;
- publicfunctionwadus():Void{
- }
- //utiliza Void si tu método no devuelve nada
Hay una no muy buena costumbre a la hora de definir el tipo de las propiedades de las clases. Por ejemplo:
- classA{
- public var myNumber:Number = 5; // bien
- public var myArray:Array = new Array(); // MAL!
- public var mySecondArray:Array;
- public function A(){
- // aquí o en cualquier otro método, bien
- mySecondArray = new Array();
- }
- }
Cuando se definen las variables de una clase sólo se puede definir el valor por defecto para las variables de tipos primitivos: String, Boolean y Number. Nada que tenga que ver con un new. El player de Flash no maneja muy esas definiciones (buscar en la sección de Enlaces “Why does my initializer get shared across all instances like it’s static?”).
[ anterior ] [ siguiente ]
Compártelo:
Visto 17.447 veces
[…] Unos apuntes […]
[…] [ anterior ] […]