Builder Pattern, Interfaces Fluidas– Patrones de diseño

lego

El builder pattern, o patrón de construcción, es uno más de los patrones creacionales de diseño. En términos generales un builder esconde los detalles de la creación de un objeto final que se llama producto. Hay varios métodos para lograr esto, y por ello hay varias “implementaciones” de este patrón que en nada coinciden , salvo en el nombre. El clásico, usado en el GoF es un poco más complejo del que vamos a ver ahora, pero no se asusten, ya tendremos la oportunidad de aprenderlo. En esta ocasión nos vamos a enfocar en un builder que se llaman interfaces fluídas (fluid interfaces).

Y seguimos con la política de que la mejor manera de entender, y de aprender, es con un ejemplo. Estuve dándole vueltas a un buen caso para ejemplificar y se me ocurrió este: supongamos que vamos a hacer un programa para una empresa que ofrece servicios de web hosting (alojamiento en web). Esta empresa tiene varios planes que ofrecer: tiene un plan personal, un plan bronce, uno plata, uno oro y finalmente el diamante. El personal ofrece un alojamiento de 10MB, una transferencia mensual de 100MB, una cuenta de correo electrónico y nada más. El bronce ofrece 100MB de alojamiento, 1000MB de transferencia mensual, 10 cuentas de correo electrónico. Así sucesivamente cada plan aumenta las cantidades de alojamiento, transferencia y cuentas de correo. A la cada plan agrega nuevas características. El plata ofrece acceso ssh, y una base de datos; el oro agrega estadísticas de sitio y panel de control. Así se van multiplicando las opciones, y para crear la aplicación definimos nuestro objeto que queda así:

package com.guisho.software.patrones.builder;

import java.math.BigDecimal;

/**
 *
 * @author guisho.com, luishernan@gmail.com
 */
public class PaqueteDeHosting {

    /*Los siguientes campos son obligatorios siempre*/
    private String nombre;
    private BigDecimal precioAnual;
    private int capacidadDeAlmacenamiento; //en MB
    private int transferenciaMensual; //en MB
    private int cantidadDireccionesCorreo;//

    /*Las siguientes son opcionales, hay planes que no los tienen*/
    private int cantidadSitiosPermitidos;
    private int cantidadBaseDeDatos;
    private String codigoOferta;
    private boolean accesoSsh;
    private boolean panelDeControl;
    private boolean estadisticasDeSitio;
    private boolean ipPublica;

    public PaqueteDeHosting(){

    }

    /* mas constructores */
    /* Setters, getters y demás código....*/
}

Bien, ahora cada plan tiene una configuración previamente establecida, que el vendedor no arma en el momento, y que preferiblemente no puede cambiar. ¿Cómo hacemos para crear cada objeto? La primera manera que se nos ocurrirá es crear un constructor pada cada caso, manteniendo siempre los valores obligatorios. Tendríamos una colección de constructores como la siguiente:

    public PaqueteDeHosting(String nombre, BigDecimal precioAnual, int almacenamiento, int transferencia, int cantidadCorreos) {
        this.nombre = nombre;
        this.precioAnual = precioAnual;
        this.capacidadDeAlmacenamiento = almacenamiento;
        this.transferenciaMensual = transferencia;
        this.cantidadDireccionesCorreo = cantidadCorreos;
    }

    public PaqueteDeHosting(String nombre, BigDecimal precioAnual, int almacenamiento, int transferencia, int cantidadCorreos, int basesDatos) {
        this.nombre = nombre;
        this.precioAnual = precioAnual;
        this.capacidadDeAlmacenamiento = almacenamiento;
        this.transferenciaMensual = transferencia;
        this.cantidadDireccionesCorreo = cantidadCorreos;
        this.cantidadBaseDeDatos = basesDatos;
    }

    public PaqueteDeHosting(String nombre, BigDecimal precioAnual, int almacenamiento, int transferencia, int cantidadCorreos, String ipPublica) {
        this.nombre = nombre;
        this.precioAnual = precioAnual;
        this.capacidadDeAlmacenamiento = almacenamiento;
        this.transferenciaMensual = transferencia;
        this.cantidadDireccionesCorreo = cantidadCorreos;
        this.ipPublica = ipPublica;
    }

    public PaqueteDeHosting(String nombre, BigDecimal precioAnual, int almacenamiento, int transferencia, int cantidadCorreos, String ipPublica,int basesDatos){
        this.nombre=nombre;
        this.precioAnual=precioAnual;
        this.capacidadDeAlmacenamiento=almacenamiento;
        this.transferenciaMensual=transferencia;
        this.cantidadDireccionesCorreo=cantidadCorreos;
        this.ipPublica=ipPublica;
        this.cantidadBaseDeDatos=basesDatos;
    }
....

Como vemos solo hicimos un par de combinaciones con la cantidad de bases de datos y la ip pública. Mientras la cantidad de campos opcionales crece, la cantidad de constructores aumenta desmedidamente creando el ambiente ideal para que aparezcan errores.

Otro camino que se puede tomar es el clásico bean: un constructor vacío y setters para cada parámetro que deseamos agregar. Este método tiene un pequeño inconveniente: podemos dejar al objeto en un estado incosistente: podemos ponerle cuántas cuentas de correo pero no ponerle nombre, ni ponerle precio. ¿Qué hacemos entonces? Hacemos un Builder!! El builder se explicará por el solo. Veamos:

En PaqueteDeHosting hacemos un constructor con los campos que siempre van para evitar estados inconsistentes:

    public PaqueteDeHosting(String nombre, BigDecimal precioAnual, int almacenamiento, int transferencia, int cantidadCorreos) {
        this.nombre = nombre;
        this.precioAnual = precioAnual;
        this.capacidadDeAlmacenamiento = almacenamiento;
        this.transferenciaMensual = transferencia;
        this.cantidadDireccionesCorreo = cantidadCorreos;
    }

Y creamos el builder

package com.guisho.software.patrones.builder;

import java.math.BigDecimal;

/**
 *
 * @author guisho.com, luishernan@gmail.com
 */
public class PaqueteDeHostingBuilder {
    private  PaqueteDeHosting paquete;

    public PaqueteDeHostingBuilder(String nombre, BigDecimal precio, int cantidadAlmacenamiento, int transferenciaMesual, int cantidadCorreo){
        this.paquete.setNombre(nombre);
        this.paquete.setPrecioAnual(precio);
        this.paquete.setCapacidadDeAlmacenamiento(cantidadAlmacenamiento);
        this.paquete.setTransferenciaMensual(transferenciaMesual);
        this.paquete.setCantidadDireccionesCorreo(cantidadCorreo);
    }

    public PaqueteDeHostingBuilder catidadSitiosPermitidos (int cantidad){
        this.paquete.setCantidadSitiosPermitidos(cantidad);
        return this;
    }

    public PaqueteDeHostingBuilder cantidadBaseDeDatos (int cantidad){
        this.paquete.setCantidadBaseDeDatos(cantidad);
        return this;
    }

    public PaqueteDeHostingBuilder accessoSsh(boolean acceso){
        this.paquete.setAccesoSsh(acceso);
        return this;
    }

    public PaqueteDeHostingBuilder panelControl (boolean panel){
        this.paquete.setPanelDeControl(panel);
        return this;
    }

    public PaqueteDeHostingBuilder codigoOferta(String codigo){
        this.paquete.setCodigoOferta(codigo);
        return this;
    }

    public PaqueteDeHostingBuilder ipPublica (String ip){
        this.paquete.setIpPublica(ip);
        return this;
    }

}

Si lo analizamos el builder simplemente envuelve al objeto que creará, con una especie de métodos de acceso (parecido a un JavaBean) pero con la peculiaridad que se devuelve a sí mismo siempre. ¿En qué nos ayuda esto? Miremos el cliente como crea un Paquete ahora:

    public static void main(String[] args) {

        PaqueteDeHosting personal = new PaqueteDeHostingBuilder("personal",new BigDecimal(100),10,100,1).build();
        PaqueteDeHosting bronce =
        new PaqueteDeHostingBuilder("bronce",new BigDecimal(200),100,1000,10).accessoSsh(true).build();
        PaqueteDeHosting plata =
        new PaqueteDeHostingBuilder("plata",new BigDecimal(300),100,1000,50).accessoSsh(true).catidadSitiosPermitidos(10).cantidadBaseDeDatos(1).build();
        PaqueteDeHosting oro =
        new PaqueteDeHostingBuilder("oro",new BigDecimal(500),100,4000,100).accessoSsh(true).catidadSitiosPermitidos(100).cantidadBaseDeDatos(5).ipPublica("10.10.10.10").build();

    }

Como vemos esto es mucho más sencillo de leer (aparte que la línea se alarga un poco, podríamos hacer varias líneas), y deja al objeto siempre en un estado consistente. Este método de construcción por medio de llamadas encadenadas se llama interfaces fluídas, y es el punto de inicio para muchos lenguajes como Groovy, que crean construcciones bastantes complejas a partir de Builders sencillos que permiten muchas configuraciones.

De nuevo, este no es el Builder de GoF, que veremos en otra ocasión, pero es otro concepto de Builder. Tiene la ventaja que es fácil de entender y de implementar. Imagino que ya se les ocurrió varias maneras de implementarlo en su código actual, así que manos a la obra y a dejar bonito el código! Finalmente les dejo el código fuente de los ejemplos aquí.
payday advance loans online

5 thoughts on “Builder Pattern, Interfaces Fluidas– Patrones de diseño

  1. Muy buen ejemplo, pero no entiendo el .BUILD(); al final de cada paquete creado

    saludos

    Reply

  2. Muy bueno!!

    Muchas gracias por lo que me toca.

    …Bueno, una cosa voy a consultar también, o no me quedo tranquilo:

    No lo he probado, pero he dado por hecho que a PaqueteDeHosting le añadimos todos los set, verdad?

    (x ejemplo: this.paquete.setCodigoOferta(codigo); …)

    Entiendo que no se pongan en el constructor por consistencia, pero que se necesitarán para la llamada del builder. ¿Estoy en lo cierto?

    He buscado el src pero no está ya en el link y, ya que "gasto post", no quería esperar a probarlo, o si tiene alguna otra explicación… no me quiero quedar en la ignorancia.

    Insisto en agradecer tu trabajo, especialmente me gusta el tema de patrones, que me parece muy útil, muy bueno, y muuuuy simplificado y resumido.

    Y viene bien como referencia cuando vamos a tocar algo a ver si se puede aplicar un patrón en lugar de continuar código sin cabeza… ;).

    Además me parece muy certera tu opinión sobre el código y el marketing, y estas cosas.

    Un abrazo.

    Reply

  3. HEy man ya no funciona tu link de descarga, buen tutorial.

    Saludos!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.