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.