Mostrando entradas con la etiqueta Patrones de diseno. Mostrar todas las entradas
Mostrando entradas con la etiqueta Patrones de diseno. Mostrar todas las entradas

domingo, 6 de diciembre de 2009

Patron Proxy en Flash

Recientemente me encontraba trabajando con Shared Objects (SO) en Flex para persistir ciertas propiedades en el cliente y empece a ver mas frecuentemente el uso de este codigo:


var settings:SharedObject = SharedObject().getLocal("/settings");

try
{
settings.data["property"] = value;
settings.flush();
}catch(e:Error)
{
...
}


Inmediatamente decidi mover esto dentro de una clase donde tenia un SharedObject y desde donde simplemente invocaba un metodo y le pasaba 2 parametros, uno con el key y otro con el value para el key, terminando con algo asi:


public function put(key:String, value:*):void
{
try
{
_sharedObject.data[key] = value;
_sharedObject.flush();
}
catch(e:Error)
{
log.error("Error while persisting setting. " + e);
}
}


Ya mi codigo empezo a verse con mucho menos lineas de codigo y tenia una clase reutilizable para interactuar con SO:

Definia esto al inicio de mi clase indicando cual shared object queria usar
var settings:SharedObjectWrapper = new SharedObjectWrapper(SharedObject().getLocal("/settings"));

Invocaba a mi clase asi:
settings.put("property", value);

Sin embargo, queria hacer algo todavia mas sencillo, mas que por funcionalidad por majaderia :) y me acorde de la clase Proxy que trae el Flash API.

Que es?

La clase Proxy que viene en el paquete flash.utils, es una clase "abstracta"(aunque no es verdad pues AS3 no permite esas clases), que quiere decir que sus metodos que usemos deben ser definidos a la hora de extender la clase y ademas no puede ser instanciada. Esta clase viene a reemplazar a Object.__resolve y Object.addPropert en AS2.

El Proxy nos permite agregar comportamiento a otras clases por medio de la definicion de ciertos metodos. Estos metodos se encargan de "escuchar" los llamados a las propiedades y servicios (funciones) en un objeto y responder a esos llamados para redireccionarlos dentro de la clase (como veremos mas abajo)

Algunos de los metodos principales definidos en la clase Proxy son:

callProperty(name:*, ... rest):*

Escucha el llamado de una funcion, los parametros son:
name -> nombre de la funcion
rest -> un Array de parametros

y retorna un valor de cualquier tipo

getProperty(name:*):*

Escucha el llamado a una propiedad de un objeto
name -> nombre de la propiedad
y retorna un valor de cualquier tipo

setProperty(name:*, value:*):void

Escucha el llamado a una propiedad de un objeto a la que se le quiera asignar un valor
name -> nombre de la propiedad
value -> valor a setear, de cualquier tipo

Que quiere decir?

Que extendiendo esta clase y algunos de los metodos definidos podemos hacer que nuestras clases que ya existan puedan tener un comportamiento adicional al que ya tenian, sobre todo si son clases hechas por otras personas o que no podamos modficar.
Vamos a ver un ejemplo, digamos que ocupamos tener un objeto dentro de nuestra clase en el cual guardaremos propiedades como un Hash. Esto lo podemos hacer facilmente haciendo esto:


var hash:Object = new Object();

hash['prop'] = value;

trace(hash.prop);


Sin embargo nuestros requerimientos nos indican que debemos logguear un mensaje indicando que propiedad esta seindo invocada y a que hora. Ahora el ejemplo talvez no tenga mucho sentido, pero sigan leyendo...

Si hicieramos esto con el codigo anterior tendriamos que poner codigo adelante de nuestros llamados (o detras) con la informacion logueada. Tambien podriamos hacer una clase que se encargue de encerrar esa logica de logguear y asignar data en un metodo, o podemos usar el Proxy:


//Primero importemos el namespace flash_proxy que es necesario para hacer override de los metodos de la clase Proxy
import flash.utils.Proxy;
import flash.utils.flash_proxy;

use namespace flash_proxy;

public class class MyProxy extends Proxy {

private var data:Object = new Object();// Este objeto contendra nuestra informacion

//Retorna undefined si la propiedad no existe
override flash_proxy function getProperty(name:*):*
{
trace("Invocando propiedad: " + name);
return data[name];
}

override flash_proxy function setProperty(name:*, value:*):void
{
trace("Agregando propiedad: " + name + " con valor " + value);
data[name] = value;
}

override flash_proxy function callProperty(name:*, ... rest):*
{
//Aqui podemos llamar a otros metodos en esta clase o de otro objeto
//que tengamos referencia dentro de la misma
if(name == "iterate")
{
trace("Contenido del objeto: ");
for each(var val:* in data) {
trace(val);
}
}
}
}


El metodo getProperty es invocado cada vez que alguna instancia de la clase MyProxy le sea invocada una propiedad, algo asi:


var proxy:MyProxy = new MyProxy();

trace(proxy.color);//Getproperty es invocado


El metodo setProperty es invocado cada vez que se setea una propiedad de la instancia:


proxy.color = "verde";//SetProperty es invocado


Y finalmente el metodo callProperty es invocado cuando hacemos esto:


proxy.iterate;


Como pueden ver no se tiene que llamar a proxy.getProperty("color") o proxy.setProperty("color", "verde") o proxy.callProperty("iterate"), sino directamente a las propiedades o nombres de metodos que queremos tener. y el FP se encarga de invocar esos metodos en tiempo de ejecucion. El se encarga de hacer la logica necesaria y la magia la ejecuta el flash player que reconoce cual metodo invocar (getProperty, setProperty, callProperty o el que sea).

Esto es super util como mencione arriba para agregar comportamiento a clases que ya existen, pensando en un AOP mucho mucho mas sencillo pero igualmente efectivo.

Pues finalmente lo que hice con mi SharedObject fue poner un comportamiento parecido al de arriba y entonces logre tratar mi interaccion con el shared object que inicialmente necesitaba al menos 5 lineas, que despues baje a la invocacion de solo un metodo a poder hacer algo como esto:


settings.property = value; //dentro de este llamado esta el try/catch y el flush del SO.


Teoria:

Esto que acabamos de ver es el patron de dise~no Proxy, que como lo indica su nombre es una objeto que toma el lugar de otro. Este patron es usado a traves del Flash Api y Flex Framework en multiples lugares como el Loader donde tenemos acceso a propiedades (width, height) aunque no tengamos acceso al contenido al mismo instante. Tambien el RemoteObject en Flex hace uso de este patron cuando invocamos operaciones directamente del remote Object, por ejemplo: ro_user.loginUser(username,password); en este caso se llama remote proxy, pero es basicamente la misma idea solo que accedemos a datos remotos o externos.

La usabilidad de este patron es bastante amplia y muchas veces es muy despreciado, pero he de admitir que es una solucion muy elegante y sencilla para muchos problemas, ademas que el Flash API nos provee las herramientas necesarias para hacer este tipo de cosas de una manera muy sencilla.

lunes, 16 de noviembre de 2009

Pseudo Threads en Actionscript

Como mencione en un post anterior el Flash Player aunque internamente tiene un sistema de Threads o hilos que permite ejecutar multiples tareas al mismo tiempo, no provee a nosotros los desarrolladores la funcionalidad para utilizar threads nativamente. Esto hace por supuesto que aplicaciones nuestras donde se procesen muchos datos o con funciones o procesamiento muy pesado haga que nuestras aplicacion se congele o se pegue...
Como desarrolladores no tenemos otra forma de hacer que esto funcion sino es emulando la funcionalidad de los threads. Digo emulando porque no hay forma de hacerlo nativamente y se hace dividiendo el proceso pesado en multiples pedazos.
Acerca de esto se ha escrito en multiples lugares con contenido muy completo y excelentes soluciones a distintos niveles de complejidad.
Las librerias anteriores y muchas otras lo que hacen es partir el proceso grande en multiples pedazos que seran ejecutados en momentos distintos. Esto disminuye la carga de trabajo y hace que el proceso no se ejecute de un solo sin permitir al FP refrescarse y por lo tanto pegarse.
Sin embargo de las librerias anteriores, mi favorita es la de Grant Skinner, se llama "Chunker" pues parte los procesos en pedazos. Sin embargo basado en esa clase, decidi hacer unas modificaciones que aqui presento:


package com.grayscale.util.threads
{
import flash.display.Shape;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.utils.getTimer;

/**
* Evento despachado cuando se concluye el proceso.
*/
[Event (type="flash.events.Event", name="complete")]

/**
* Esta clase es una implementacion simple de un pseudo thread en AS3.
*

Esta basada en la clase de Grant Skinner aunque con algunas modificaciones.
* http://www.gskinner.com/libraries/Chunker.zip


*/
public class SimpleThread extends EventDispatcher
{
//Nos permite tener acceso al evento ENTER_FRAME que sera el que lleve el
//paso de cada cuando se ejecutaran nuestros procesos.
private var shape:Shape = new Shape();
//Estado del hilo
private var _paused:Boolean = false;
//Cada cuanto se ejecutara
private var _execTime:uint;
//Funcion a ejecutar
private var _func:Function;
//Argumentos a nuestra funcion. Es un array de elementos que pasariamos a nuestra funcion
private var _args:Array;

/**
* Constructor.
*
* @param execTime Cada cuanto se ejecutara nuestra funcion
* @param func funcion a ejecutar
* @param args parametro opcional con los valores a pasarse en la funcion
*/
public function SimpleThread(execTime:int, func:Function, args:Array=null)
{
_execTime = execTime;
_func = func;
_args = args;

pause = false;
}

/**
* Para el thread o lo arranca
*/
public function set pause(value:Boolean):void
{
_paused = value;
if(_paused) shape.removeEventListener(Event.ENTER_FRAME, run);
else shape.addEventListener(Event.ENTER_FRAME, run);
}


/**
* Metodo que ejecuta nuestra funcion
*/
private function run(event:Event):void
{
var initialTime:int = getTimer();//registra el tiempo donde inicia este iteracion
while(!_paused && (getTimer() < _execTime+initialTime ))//revisa si no hemos excedido la cantidad de tiempo del inicio
{
//llama nuestra funcion y esta tiene que regresar un valor booleano. Si este es falso detiene el thread sino continua.
var res:Boolean = _func.apply(this, _args) as Boolean
if(!res)//ya se termina de ejecutar nuestra funcion, pausamos el thread y despachamos evento avisando que se concluyo
{
dispatchEvent(new Event(Event.COMPLETE));
pause = true;
}
}
}
}
}


El unico requisito para esta clase, es que la funcion que queremos partir debe devolver un valor booleano: true si todavia hay data que procesar, false si ya termino de procesar la informacion.
A continuacion presento dos ejemplos, ambos cargan el mismo archivo(analytics de este blog); uno lo hace sin los pseudo-threads y otro con el pseudo-thread. Podran ver la diferencia como uno se pega y el otro no. Es un archivo grande (4mb) asi que tengan paciencia. Apenas se carga se activa el boton de "Start"
Notas: 1. Debe aparecer un spinner cuando se aprieta el boton de "start". 2. los controles de la pantalla deben ser accesbles mientras se procesan los datos.

Sin Thread.









Con Thread.







Al partir nuestra funcionalidad en multiples ejecuciones y no solo en una, ocupamos una forma de llegar a saber donde terminamos de procesar y desde donde debemos empezar. Para esto puedo sugerir el uso del patron Iterator. Este patron permite a una coleccion encapsular la manera en que se recorre la coleccion y guardar dentro de si misma cual fue el ultimo elemento procesado, sin tener que tener mantener un indice de la coleccion por fuera. El iterador se encarga de todo. Esto es bueno ya que al partir nuestro proceso ocupamos mantener una referencia al dato que estamos procesando sin tener que crear otras variables.
Aqui presento las siguientes clases para tener nuestro iterator.

Interfaz Base:


package com.grayscale.util.iterator
{
/**
* Esta interfaz define los metodos basicos que debera contener un iterador en nuestra coleccion
* @author Ivan Ramirez - ivan.ramirez@gmail.com
*/
public interface IIterator
{
/**
* Devuelve "true" si tenemos mas valores que recorrer dentro de nuestra coleccion
*/
function hasNext():Boolean;
/**
* Devuelve el proximo valor en nuestro iterador
* @returns el siguiente objeto en la coleccion.
*/
function next():*;
/**
* Nos permite reiniciar el recorrido de nuestra coleccion
*/
function reset():void;
}
}


Implementacion basica:


package com.grayscale.util.iterator
{
/**
* Implementacion basica de nuestro iterator para el manejo de arrays. Podemos implementar esta interfaz
* en otras clases para hacer iteradores con Strings o de XML, etc.
* @author Ivan Ramirez - ivan.ramirez@gmail.com
*/
public class ArrayIterator implements IIterator
{
//Coleccion base
private var collection:Array = new Array();
private var index:int;

public function ArrayIterator(values:Array)
{
collection = values
}

public function hasNext():Boolean
{
return index < collection.length;
}

public function next():*
{
return collection[index++];
}

public function reset():void
{
index = 0;
}

}
}


La ventaja de tener una interfaz definiendo el iterador, es que podemos crear nuestras propias implementaciones del iterador de acuerdo a nuestras necesidades.
Ya con nuestro iterador cargado con la coleccion de datos que tenemos que procesar, procedemos a utilizar nuestro thread.

El codigo de las aplicaciones de este ejemplo esta aqui.