Lo que yo sé de AS2 #Tus amigos los patrones
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
Tus amigos los patrones
A mi me gusta pensar en los patrones como soluciones que gente muy friki y muy gafotas sabe para el problema que tienes entre las manos. Sólo son formas de organizar el código que te solucionan elegantemente un problema.
Ahora, como dijo un día Xavi Beumala (code4net.com): “si no sientes que necesitas un patrón, es que no lo necesitasâ€. Es decir, no se trata de que busques problemas para aplicar un patrón, sino de que apliques patrones a situaciones y problemas reales. Lo más difícil de los patrones es saber reconocer cuándo son necesarios.
NOTA IMPORTANTE: Las implementaciones de los patrones que voy a comentar a continuación son “libresâ€. Es decir, no me importa tanto que desde un punto de vista formal cumplan el patrón al 100% sino que guarden su “esencia†de la forma más simple posible. Si realmente estás interesado en conocer a fondo los patrones te recomiendo el libro Head First Design Patterns o si eres un tío muy gafotas, la biblia que todo talibán de los patrones lee los domingos por la mañana para desayunar: Design Patterns (by the Gang of four). Enlaces al final.
Singleton
¿Cuándo? Cuando necesitas que en una aplicación haya una y sólo una instancia de una clase.
¿Ejemplo? Un controlador de Combos. Supongamos que estamos desarrollando nuestro propio componente ComboBox, normalmente no queremos que haya 2 abiertos al mismo tiempo. Podríamos controlarlo mediante variables globales, pero ya hemos visto que eso no es muy buena idea. Singleton al rescate. La idea es tener una clase Singleton que tenga una referencia a todos los combos creados y que se encargue de coordinarlos.
Aquí va la clase que coordina, el Singleton
- import Combo;
- class ComboManager{
- private static var _instance:ComboManager;
- private var combos:Array;
- private function ComboManager(){
- combos = new Array();
- }
- public static function getInstance():ComboManager{
- if(_instance == null){
- _instance = new ComboManager();
- }
- return _instance;
- }
- public function addCombo(combo:Combo):Void{
- combos.push(combo);
- }
- }
Y aquí la clase combo que lo utilizaría;
- import ComboManager;
- class Combo{
- public function Combo(){
- var cm:ComboManager = ComboManager.getInstance();
- cm.addCombo(this);
- }
- }
Comentarios
Es muy importante darse cuenta de que el constructor del Singleton es privado, por lo que nadie puede crear una instancia a la forma tradicional (con new). La única forma de obtener una instancia del Singleton es utilizar el método público estático getInstance. Y es dentro de getInstance donde se controla que la instancia sólo se cree una vez, el resto de veces se devuelve la ya creada.
Problemas
Como con todos los patrones, no se debe abusar del Singleton. Especialmente NO tienen que ser un un sustituto de la variables globales. En la sección de Enlaces, al final del artículo de la Wikipedia hay enlaces sobre este problema.
Factory Pattern e interfaces
Antes de comentar el Factory, vamos a explicar qué es una interface. Una clase tiene que tener todos los métodos de la interface (o interfaces) que implemente. Ejemplo:
- interface Application{
- public function config(appXML:XML):Void;
- public function start():Void;
- }
- class VideoPlayer implements Application{
- public function config(appXML:XML):Void{
- }
- public function start():Void{
- }
- }
Si la clase VideoPlayer no tuviera los 2 métodos de la interface, el compilador protestaría. Así que las interfaces son una forma de “obligar†a que las clases que la implementen tenga unos métodos públicos conocidos.
Lasintefaces sólo definen métodos públicos, no pueden definir ni propiedades, ni métodos privados o estáticos.
Ahora el Factory pattern.
¿Cuándo? Cuando necesitas resolver el mismo problema de distintas formas, y además hay que decidir en tiempo de ejecución cuál utilizar.
¿Ejemplo? Una aplicación para hacer un reloj que necesita 2 vistas distintas, una digital y otra analógica. Además necesitamos decidir en tiempo de ejecución cual utilizar.
Primero definimos una sencilla interface que ambas vistas van a implementar.
- interface iView{
- public function config():Void;
- public function draw():Void;
- }
Luego creamos las 2 vistas distintas y hacemos que implementen la interface.
- import iView;
- class AnalogView implements iView{
- public function AnalogView(){}
- public function config():Void{}
- public function draw():Void{
- // la clase analógica pinta las manillas
- }
- }
- import iView;
- class DigitalView implements iView{
- public function DigitalView(){}
- public function config():Void{}
- public function draw():Void{
- // la clase digital pinta números
- }
- }
Ahora creamos el modelo. Es importante fijarse en que el modelo no sabe con qué implementación de iView va a trabajar, ni siquiera importa las vistas. Es decir, no sabe si será analógico o digital. Lo único que sabe es que va a trabajar con una instancia de iView. Es ahí donde está la flexibilidad, es lo que se llama trabajar para la interface y no para implementaciones.
- import iView;
- import ViewFactory;
- class ClockModel{
- private var view:iView;
- public function ClockModel(){
- // supongamos que viene de un xml, FlashVars o algo dinámico
- var viewType:String = “digitalâ€;
- // pedimos a la factoría una vista
- view = ViewFactory.getView(viewType);
- view.config();
- view.draw();
- }
- }
Y finalmente la factoría:
- import iView;
- import AnalogView;
- import DigitalView;
- class ViewFactory{
- public static function getView(viewType:String):iView{
- var v:iView;
- if(viewType == “analogâ€){
- v = new AnalogView();
- } else {
- v = new DigitalView();
- }
- return v;
- }
- }
Vista la estructura de la factoría (un if o switch gigante), es fácil darse cuenta de que si en el futuro queremos añadir más tipos de vistas, simplemente hay que aumentar el número de casos en el if. Lo bueno es que el modelo seguirá trabajando sin problemas mientras que las nuevas vistas implementen iView.
Esto mismo se podría conseguir en lugar de con una interface haciendo que ambas vistas extiendan de una clase común. Entonces el modelo trabajaría con esa vista común en lugar de con la interface, pero el resultado y objetivo es el mismo. ¿Cuándo utilizar una interface y cuándo extender? Ver explicación en la sección de apuntes.
Comentarios
Situaciones en las que un factory es aplicable hay muchas. Todas ellas tienen es común distintas formas (implementaciones) de solucionar un mismo problema. Por ejemplo supongamos que tenemos una aplicación que necesita leer y escribir el sistema de archivos del usuario. Además queremos que la aplicación funcione on line y off line. Para conseguirlo on-line nos tendremos que basar en algún lenguaje de servidor, y para hacerlo on-line necesitaremos, Screenweaver, SWFStudio o algún otro wrapper o contenedor de swfs.
Veamos una posible interface:
- interface iWrapper{
- // los callbacks son porque la mayoría de estas llamadas
- // serán asíncronas
- public function getDir(path:String,callback:Function):Void;
- public function writeFile(path:String,callback:Function):Void
- public function readFile(path:String,callback:Function):Void;
- }
La interface está definiendo qué queremos que el wrapper haga. Cómo lo haga es tarea de cada una de las implementaciones. En el caso de la implementación de servidor, seguramente sea con llamadas sendAndLoad del objeto XML, en el caso de la implementación local con Screenweaver será a través de las clases AS2 que vienen con la instalación. La parte buena es que si la aplicación trabaja con una instancia de iWrapper, cambiar la de on-line a off-line no sería problema.
Problemas
Uno de los efectos secundarios de la factoría es que TODAS las implementaciones se compilan en el swf final, aunque sólo vayamos a utilizar una. ¿Y no hay forma de evitarlo? La hay. Como el tema es un poco más avanzado, quien quiera más información sobre el tema puede seguir 2 tutoriales de la sección de Enlaces que se titulan “Crear instancias dinámicamente†y “Cargar clases dinámicamenteâ€.
Modelo-VistaControlador
¿Cuándo? Siempre. Bueno, casi siempre. Por poco grande que sea una aplicación siempre es útil separar lógica de presentación. Y eso es la parte fundamental del MVC, separar lógica de presentación.
A pesar de que ya lo he dicho un par de veces, la implementación que pongo a continuación NO es pura, es una adaptación libre y pragmática. Prefiero repetirlo para que:
- Nadie me salte al cuello con la implementación buena en la mano.
- Tú, querido lector, te molestes en ir a buscar esa implementación buena y compares. En la sección de Enlaces podrás encontrar más información.
Dicho esto, comentarios sobre problemas que pueda tener mi implementación son MUY bienvenidos. Mi implementación por ejemplo, asume que sólo hay una vista. eso permite al modelo hacer llamadas directas a la vista sin necesidar de que la vista sea listener del modelo. Además tambien junto en una misma clase la vista y el controlador. Veamos el ejemplo:
Empezamos con una clase que se encarga de crear la instancia del modelo:
- import tv.zarate.Projects.loqueyosede.Model;
- class tv.zarate.Projects.loqueyosede.MVCApplication{
- public static function main(m:MovieClip):Void{
- var model:Model = new Model();
- model.config(m);
- model.start();
- }
- }
Luego, el modelo:
- import tv.zarate.Projects.loqueyosede.View;
- class tv.zarate.Projects.loqueyosede.Model{
- private var view:View;
- private var timeLine_mc:MovieClip;
- private var view_mc:MovieClip;
- private var numberOfClicks:Number = 0;
- public function Model(){}
- public function config(m:MovieClip):Void{
- // el modelo se guarda una referencia a la linea de tiempo principal
- // basicamente para poder acceder a las FlashVars
- // cuando sea necesario
- timeLine_mc = m;
- // creamos un mc solo para la vista
- view_mc = timeLine_mc.createEmptyMovieClip("view_mc",100);
- }
- public function start():Void{
- // creamos la instancia de la vista
- view = new View();
- // le pasamos la referencia al modelo y el
- // clip sobre el que va a trabajar
- view.config(this,view_mc);
- // en este caso directamente llamamos al metodo
- // start de la vista
- // en aplicaciones mas complejas, el modelo
- // podria primero ir a buscar datos a un servidor
- // o leer un xml de configuracion
- view.displayValue(numberOfClicks);
- }
- public function updateClick():Void{
- // la vista llama a este metodo cada vez que el usuario hace click
- numberOfClicks++;
- // despues de actualizar
- // llamamos al metodo que nos interesa de la vista
- view.displayValue(numberOfClicks);
- }
- }
Y por último la VistaControlador (abreviado, ver código adjunto para la versión completa):
- import tv.zarate.Projects.loqueyosede.Model;
- class tv.zarate.Projects.loqueyosede.View{
- private var model:Model;
- private var base_mc:MovieClip;
- private var background_mc:MovieClip;
- private var title_mc:MovieClip;
- private var titleField:TextField;
- private var width:Number = 0;
- private var height:Number = 0;
- private var OVER:String = "over";
- private var OUT:String = "out";
- private var PRESS:String = "press";
- public function View(){}
- public function config(_model:Model,_base_mc:MovieClip):Void{
- // nos llega por composicion una instancia del modelo
- // eso nos permitira acceder a sus metodos y propiedades publicas
- model = _model;
- // guardamos una referencia al clip con el que trabajara la vista
- base_mc = _base_mc;
- // creamos los elementos basicos
- doInitialLayout();
- onResize();
- }
- public function displayValue(value:Number):Void{
- titleField.text = "Clicks > " + value;
- }
- private function doInitialLayout():Void{
- var button_mc:MovieClip = base_mc.createEmptyMovieClip("button_mc",300);
- // definimos los eventos y los delegamos al metodo manageButton
- button.onPress = Delegate.create(this,manageButton,button,PRESS);
- }
- private function layout():Void{}
- private function onResize():Void{}
- private function manageButton(mc:MovieClip,action:String):Void{
- switch(action){
- case(PRESS):
- // llamamos a un metodo publico del modelo
- // que actualiza el contador de clicks
- model.updateClick();
- }
- }
- }
Comentarios
Utilizo la clase MVCApplication como “punto de partida†(aunque también se podría integrar en la clase Model). Primero crea la instancia del modelo, luego lo configura mediante config, y luego llama al método start. Tanto “config†como “start†son nombres completamente arbitrarios.
Ya en el modelo, en el método start se crea la instancia de la vista, se la configura pasando una referencia al modelo y un MovieClip de trabajo.
En este momento, tanto la vista como el modelo tienen una referencia mútua, con lo que pueden fácilmente acceder a sus propiedades y métodos públicos. Es importante darse cuenta de que en esta implementación sólo puede haber una vista. ¿Y es eso importante? Pues seguro que depende de a quien le preguntes. Desde luego es menos flexible (no puede haber 2 vistas al mismo tiempo), pero yo creo que es más sencillo a la hora de ser usado.
Después de configurar la vista, es el modelo quien decide qué hacer. En este caso tan simple tampoco tiene muchas opciones, por eso llama directamente al método displayValue al que le pasa el total de clicks hechos. En aplicaciones más complejas, lo más normal es que primero tenga que hacer algo de tarea sucia como leer un xml de configuración, extraer algún parámetro de las FlashVars, instanciar otras clases relativas al modelo, etc. etc.
En la vista mostramos el número de clicks y creamos un botón para aumentarlo. Es en este momento donde se supone que debería entrar en juego el controlador. Si tuvieramos un controlador como tal, la función manageButton seguramente debería encontrarse allí. Es decir, el controlador es el encargado de decidir qué hacer con las acciones del usuario.
Aún sin el controlador hemos conseguido separar la lógica (el contado de los clicks), de la presentación (cómo la vista muestra la información). Al modelo le da exactamente igual cómo se muestre el número de clicks (si centrado en la pantalla, si en verde, rojo, grande, pequeño) y la vista tampoco le importa mucho cómo se consigue esa información. Símplemente quiere que le digan qué tiene que pintar.
[ anterior ] [ siguiente ]
Compártelo:
Visto 6.239 veces
[…] Tus amigos los patrones […]
[…] [ anterior ] [ siguiente ] […]