Patrón de diseño Proxy en Java

El patrón de diseño Proxy es simplemente un intermediario que se diseña sobre una clase para agregar alguna funcionalidad adicional, sin modificar la clase original.

El patrón Proxy nos dice que construyamos una nueva clase proxy con la misma interfaz que tiene la clase original. Luego en vez de utilizar la clase original, utilizamos la clase proxy en todos los clientes que antes usaban la clase original. La clase proxy agrega la funcionalidad deseada y luego delega todo el trabajo a la clase original.

Para crear este patrón necesitamos definir una interfaz de la cual extenderá la clase principal. Luego crearemos nuestra clase proxy que también extenderá de la interfaz y tendrá como atributo la clase principal.

Cuales son las partes del patrón de diseño Proxy

Este patrón se conforma principalmente de tres partes.

  • Interfaz: de la que se creará la clase principal.
  • Clase principal: que implementa la interfaz.
  • Clase proxy: la clase que también extenderá de la interfaz y que contendrá como atributo la clase principal.

proxy-pattern

Como se crea el patrón de diseño Proxy en Java

Para comprender mejor este patrón veamos un ejemplo. Supongamos que tenemos una clase que realiza procesos para los cuales tiene que leer un archivo grande en disco. Deseamos que esta clase levante el archivo en disco una sola vez, pero no tenemos acceso a modificar la clase original.

Crearemos a partir de la interfaz nuestra clase. Además de este misma interfaz implementaremos el proxy. El cliente en vez de consultar la clase real, lo hará atraves del proxy.

proxy-pattern

Tenemos nuestra interfaz de la cual extiende la clase original.

package patterns.proxy;

public interface BatchProcess {

    void process();

}

La clase que realiza el proceso en el constructor lee el archivo desde el disco. En esta clase simulamos en el método loadFromDisk el trabajo de leer el archivo.


package patterns.proxy;

public class BatchProcessImpl implements BatchProcess {

    private final String fileName;

    public BatchProcessImpl(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }

    @Override
    public void process() {
        System.out.println("Processing file " + fileName);
    }

    private void loadFromDisk(String fileName) {
        System.out.println("Expensive and hard process...");

        for (int i = 0; i < 10; i++) {
            System.out.println("Loading " + fileName + " " + i * 10 + "%");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }

        System.out.println("Loaded " + fileName);
    }
}


Este proceso de lectura en la clase BatchProcessImpl sabemos que es costoso y quisiéramos que se realice una sola vez. No tenemos acceso a modificar la clase BatchProcessImpl, por lo que decidimos crear una clase proxy. La clase proxy implementa la misma interfaz BatchProcess y tendrá como atributo la clase BatchProcessImpl

package patterns.proxy;

public class ProxyBatchProcess implements BatchProcess {

    private BatchProcessImpl batchProcess;
    private String fileName;

    public ProxyBatchProcess(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void process() {
        if (batchProcess == null) {
            batchProcess = new BatchProcessImpl(fileName);
        }
        batchProcess.process();
    }
}

Probamos ahora el patrón de diseño Proxy

  • Creamos el objeto ProxyBatchProcess enviando un archivo grande.
  • Ejecutamos por primera vez y vemos en la salida que el archivo se lee.
  • Luego volvemos a ejecutar y esta vez podemos comprobar que ya no se lee el archivo y solo se realiza el proceso sobre el archivo previamente ‘que fue leído desde el disco’.
package patterns.proxy;

public class ProxyPatternExample {

    public static void main(String[] args) {

        BatchProcess batchProcess = new ProxyBatchProcess("big_file.txt");

        //batchProcess will be loaded from disk
        System.out.println("BatchProcess will be loaded from disk");
        batchProcess.process();

        System.out.println("-----------------");

        //batchProcess will not be loaded from disk
        System.out.println("BatchProcess will not be loaded from disk");
        batchProcess.process();
    }
}

En la salida vemos que la primer ejecución ‘carga’ el file y en la segunda el proceso se realiza sin volver a ‘cargarlo’ tal como deseábamos.


BatchProcess will be loaded from disk
Expensive and hard process...
Loading big_file.txt 0%
Loading big_file.txt 10%
Loading big_file.txt 20%
Loading big_file.txt 30%
Loading big_file.txt 40%
Loading big_file.txt 50%
Loading big_file.txt 60%
Loading big_file.txt 70%
Loading big_file.txt 80%
Loading big_file.txt 90%
Loaded big_file.txt
Processing file big_file.txt
-----------------
BatchProcess will not be loaded from disk
Processing file big_file.txt

Algunas ventajas y desventajas de este patrón:

  • Podemos modificar funcionalidad sin afectar a otras funcionalidades que esten usando la clase original directamente.
  • El código se puede volver complicado si es necesario introducir gran cantidad de funcionalidad nueva.
  • La respuesta real del servicio puede ser desconocida hasta que realmente se la invoca.

Conclusión:

Hemos visto el patrón Proxy y lo útil que puede resulta para agregar funcionalidad a una clase sin modificarla.

Puedes descargar este código en GitHub

Hi! If you find my posts helpful, please support me by inviting me for a coffee :)

Ver también