Patrón "Proxy"



Introducción

El proxy es útil si queremos añadir o modificar comportamiento de una clase (la llamaremos "objeto real") sin tener que modificarla. El proxy es un intermediario que implica un recubrimiento del objeto real.


Un ejemplo

Tenemos un interface para escribir y leer de un soporte:


public interface Soporte {
	public void escribir( String mensaje );
	public String leer( int numLinea );
	public Vector leer();
}

Además tenemos una clase, denominada Pizarra, que implementa el interface Soporte y que en su método escribir(String mensaje) simplemente añade el argumento a un Vector. El método leer(int numLinea) devuelve la cadena cuyo orden dentro del vector es el argumento. El cliente (main) pueda insertar (escribir) y obtener (leer) cadenas.


import java.util.*;

public class Pizarra implements Soporte {
	private Vector mensajes = new Vector();

	public void escribir(String mensaje) {
		mensajes.add(mensaje);
	}

	public String leer(int numLinea) {
		return mensajes.get(numLinea);
	}

	public Vector leer() {
		return mensajes;
	}
}

El proxy es un intermediario (que también implementa el interface del objeto real) y que nos permite añadir o modificar comportamiento sin reescribir el objeto real. En escribir() del proxy añado el número de línea y delega en la Pizarra el resto de comportamiento. Es interesante observar que este patrón evita abusar del uso de herencia. Este abuso es la primera tentación del novato, ya que piensa: "si quiero modificar el comportamiento de una clase hago el nuevo comportamiento en una clase hija". No es que la herencia sea de partida ilegal o inconveniente, simplemente se trata de no abusar de ella aplicandola a todo tipo de problema. De esta forma no caeremos en el vicio de aquel que sólo sabía usar martillos y todos los problemas le parecían clavos.


import java.util.Vector;

public class ProxyDePizarra implements Soporte {
	private Soporte soporte;

	public ProxyDePizarra() {
		this.soporte = new Pizarra();
	}
	public ProxyDePizarra( Soporte soporte) {
		this.soporte = soporte;
	}
	public void escribir( String mensaje ) {
		String linea = String.valueOf(soporte.leer().size()+1) + " " + mensaje;
		soporte.escribir( linea );
	}
	public String leer( int numLinea ) {
		return soporte.leer( numLinea );
	}
	public Vector leer() {
		return soporte.leer();
	}
}

Un aspecto importante del cliente es que sólo utiliza el tipo Interface Soporte para acceder al proxy, con lo que conseguimos generalidad:


public class Inicio {
	static public void main(String[] args) {
		try {
			//// Creamos el proxy
			Soporte proxy = Factoria.getPizarra("ProxyDePizarra");

			//// Escribimos (el proxy inserta número de línea)
			proxy.escribir("En un lugar de La Mancha");
			proxy.escribir("de cuyo nombre no quiero acordarme");

			for (String str : proxy.leer())
				Visor.mostrar(str);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

La factoria simple es otro patrón importante. Una factoría se centra en ocultar la instanciación de una clase. En nuestro caso anterior devuelve un objeto del tipo ProxyDePizarra:


/************************************************************************************
 * Factoria de pizarra (objeto real o proxy). Recibe el nombre de la clase
 * (sin especificar paquete) y crea y devuelve el objeto (real o proxy).
 ***********************************************************************************/
public class Factoria {
	static public Soporte getPizarra( String nomClase ) throws Exception {
		Class clase = Class.forName( Factoria.class.getPackage().getName() + "." + nomClase);
		return (Soporte) clase.newInstance();
	}
}

Nota: Para que el newInstance() de la Factoria pueda funcionar debe existir un constructor sin argumentos en el ProxyDePizarra. El visor es algo elemental, tan sólo sirve para diferenciar el modelo de la vista:


public class Visor {
	static public void mostrar( String mensaje ) {
		System.out.println( mensaje );
	}
}

El resultado final sería:


1 En un lugar de La Mancha
2 de cuyo nombre no quiero acordarme

Otras características

El proxy y el objeto real comparten interface. El proxy debe tener una referencia al objeto real.

Ya hemos dicho que el proxy es un intermediario que implica un recubrimiento del objeto real para añadir o modificar comportamiento. Especialmente apropiado cuando estamos en situaciones en las que tenemos que elegir entre las operaciones del objeto real y las del proxy. Aunque en el ejemplo anterior no hacemos instancia del objeto real ni permitimos ningún acceso directo a él, podriamos tener dos tipos de acceso: uno directo al objeto real y otro al proxy.


			Soporte piz = Factoria.getPizarra("Pizarra");
			Soporte proxy = new ProxyDePizarra(piz);
			piz.escribir("XYZ"); // NO añade nº de línea
			proxy.escribir("QQQ"); // SI añade nº de línea


Volver al índice