3 sept 2009

Proxy object

Los objetos proxy constituyen un concepto avanzado que se asemeja por completo a los más conocidos servidores proxy, los cuales actúan como intermediarios entre el cliente y el servidor. En nuestro caso, un objeto proxy constituye una especie de envoltorio "invisible" (wrapper) sobre un objeto que es el que al final queremos invocar.

Probablemente te preguntes para qué te sirve esto, la verdad es que cuando se conoce bien, uno se pregunta como es posible no haber conocido de su existencia previamente. En resumidas cuentas, siempre que tengamos que hacer algo adicional a una simple llamada a un método de un objeto, el Proxy es un buen candidato a cubrir esta necesidad.

Una razón para controlar el acceso a un objeto, es precisamente aplazar la creación e inicialización del mismo hasta que realmente lo necesitamos (obviamente en aquellos casos en los cuales esto tenga un coste significativo). Un ejemplo puede ser la carga de un documento que contiene además de texto, multitud de imágenes incrustadas. Obviamente no es necesario cargar todas las imágenes al principio, ya que con un alto grado de probabilidad no serán utilizadas inmediatamente. En este caso, conviene realizar la carga bajo demanda, y es donde entra el Proxy ya que a todos los efectos se puede considerar como un objeto del tipo que enmascara (Imagen) pero con capacidad de programar e interceptar la llamada al método que deseemos. Imaginemos que el método es "draw", en este caso aprovechamos en el proxy para crear el objeto Imagen y a continuación invocar a "draw".

Según el tipo de uso, hay gente que clasifica los objetos Proxy en alguna de las siguientes categorías:
  • Remote proxy, que se encarga de codificar la petición y sus argumentos para enviar la solicitud y recibir la respuesta del objeto real. Un ejemplo de esto suelen ser las llamadas a Web Services, que nos aislan de toda la complejidad de tratar con las peticiones sobre la capa de transporte elegida (http, smtp, ...)
  • Virtual proxy, para dotar de un nivel de cache al objeto que se está llamado. Imagina aquellas situaciones en las cuales la respuesta del objeto real es la misma ante la misma entrada, el proxy puede cachear las respuestas e invalidar las mismas en base a determinados criterios
  • Protection proxy, para verificar si la llamada tiene suficientes privilegios como para poder realizarse

Bueno, vista la parte "teórica", quizás esté bien ver un poco de código ¿no crees?

package Proxy;
use strict;
use Carp;
use UNIVERSAL qw/can/;


our $AUTOLOAD;


sub new {
my ($class, $target) = @_;
return bless {TARGET=>$target}, $class;
}


sub isa {
my $self = shift;
return $self->{TARGET}->isa(@_);
}


sub AUTOLOAD {
my $self = shift;
my @params = @_;
my $method = substr($AUTOLOAD,7);
return if( $method eq 'DESTROY' );
my $target = $self->{TARGET};
if( defined $target ) {
if( UNIVERSAL::can($target, $method) ) {
return $target->$method(@params);
}
else {
croak "Proxy target doesn't support $method method";
}
}
else {
croak "Proxy target hasn't been set $target";
}
}

1;

Como puedes observar, se trata de la definición de un módulo Proxy que hace uso del método especial AUTOLOAD de Perl. Recuerda, esta función (si existe) se llama cada vez que no se encuentra un método que se está intentando llamar. En AUTOLOAD, simplemente se verifica que el método existe en el objeto destino, y si es así, se llama.
Para probar esto, imaginemos que tenemos otra clase Persona, muy sencilla (con ayuda de Class::Accessor):

package Persona;
use base qw(Class::Accessor);


Persona->mk_accessors(qw(name role salary));

1;


Bueno, hasta ahora nada del otro mundo. Imaginemos ahora que nos piden que la propiedad "salary", cada vez que sea consultada, debe quedar constancia en un fichero de quien lo hizo. Nuestra primera intención podría ser ir a tocar la clase Persona y meter el código donde corresponda. Sin embargo, a mi juicio, eso "ensuciaría" la clase con cuestiones que no le son propias. Y además para algo tengo que utilizar el tema del Proxy, que para eso lo he explicado...

En primer lugar, creamos una clase que hereda de la anterior y que hace el trabajo de log en el fichero:
package Proxy::Persona;

use base 'Proxy';
use strict;
use autodie;

sub salary {
my $self = shift;
my @params = @_;
if( @params ) {
$self->{TARGET}->salary($params[0]);
}
else {
open my $fh, ">>person.txt";
my @now = localtime(time);
printf $fh "[%02d-%02d-%d] %10s %s\n", $now[3], 1+$now[4], 1900+$now[5], getlogin, $self->{TARGET}->salary;
close $fh;

return $self->{TARGET}->salary;
}
}
1;


Y a continucación creamos un fichero para probar que esto ha funcionado:
use Persona;
use Proxy::Persona;

$p = new Persona;

$p->name('JOSE');
$p->role('MANAGER');
$p->salary(2000);

$x = Proxy::Persona->new($p);
print "Salario: ", $x->salary(), "\n";


Precisamente el último print, es el que sirve para llamar al método proxetizado y hacer el log. Cualquier otra llamada a cualquier otro método de la clase Persona, se llama sin mayor actuación. Es de destacar:

  • La clase target (Persona) no se ha modificado para nada
  • El script que utiliza la clase proxy debe ser modificado ligeramente para crear un objeto de la clase Proxy::Persona
  • Toda la funcionalidad que deseemos añadir, figura en la clase Proxy::Persona, con lo cual queda mucho más limpio el resto del código que empleamos.

Bien, espero que con esta pequeña aportación, haya quedado mucho más claro para que sirve y cómo podemos beneficiarnos del uso del patrón Proxy.

Me gustaría conocer tus opiniones, no únicamente de este tema, sino de la línea de los artículos, o si hay algún tema que sea de tu interés y que pudiese ser motivo de una entrada. ¡Animo!

No hay comentarios:

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.