DEV Community

Jorge Eψ=Ĥψ
Jorge Eψ=Ĥψ

Posted on • Originally published at jorge.aguilera.soy on

Builders

En el post anterior vimos cómo usar Factorias y las ventajas que estas pueden aportar sobre los constructores

En este post vamos a ver otra forma de construir objetos, los Builders.

"Consider a builder when faced with many constructor parameters"

Tanto el uso de factorías como el de constructores tienen un problema cuando el número de parámetros para construir una clase empieza a ser elevado

NOTE

Y cúando es elevado? pues cada cual tendrá su opinión pero en mi opinión más de tres parámetros ya es una señal de que esa clase va a necesitar más bien pronto que tarde otro parámetro más y sería buena idea aplicar un Builder

INFO

Cuando, tanto con factorías como constructores, empiezas a crear métodos con parámetros necesarios y luego otros métodos en los que vas añadiendo cada vez un parámetro opcional se llama "telescoping constructor"

class A{
    public static A newInstance(){
        return new A();
    }
    public static A newInstance(String nombre){
        return new A(nombre);
    }
    public static A newInstance(String nombre, int edad){
        return new A(nombre, edad...);
    }
    public static A newInstance(String nombre, int edad, Date nacimiento){
        return new A(nombre, edad...);
    }
    public static A newInstance(String nombre, int edad, Date nacimiento, Ciudad ciudad){
        return new A(nombre, edad...);
    }
    ...
}
Enter fullscreen mode Exit fullscreen mode

Otra forma de inicializar objetos cuando el número de parámetros es numeroso es aplicando la aproximación de JavaBeanscreando métodos get/set para cada propiedad de la instancia. Sin embargo esta forma tiene muchos problemas como por ejemplo la validación de que todos los parámetros han sido proporcionados, es muy "verbose", dificil de seguir y no puede crear objetos inmutables.

Builder

Por todo ello el otro patrón que se aplica es creando un Builder

class A{

    private final int prp1;
    private final int prp2;
    ...
    private final int prpn;

    public int getPrp1() { return prp1;}
    // NO tenemos metodo setPrp1

    public static class Builder{
        private final int prp1;
        private final int prp2;
        ...
        private final int prpn;

        public Builder(){}

        public Builder prp1(int p){ prp1 = p; return this;}
        ...
        public Builder prpn(int p){ prpn = p; return this;}

        public A build(){
            // validar que tenemos todos los parametros necesarios
            return new A(this);
        }
    }
    private A(Builder builder){
        prp1 = builder.prp1;
        ...
        prpn = builder.prpn;
    }
}
Enter fullscreen mode Exit fullscreen mode

y el código para crear una instancia A sería:

A a = new A.Builder().prpn(n).prp2(2).prp1(1).build();
Enter fullscreen mode Exit fullscreen mode
TIP

Si por ejemplo la clase requiere de unos parámetros requeridos es fácil pedirlos en el constructor del Builder y declarar así explicitamente que son necesarios.

"The Builder pattern is well suited to class hierarchies"

Esta capacidad del patrón Builder no es fácil de ver de primeras (al menos a mí) pero es muy potente

Cuando tenemos una jerarquía de objetos tipo clase base "A" y otras clases que extienden de ella, "B" y "C", podemos aplicar el patrón Builder usando genéricos de tal forma que reutilizemos al máximo los builders

public abstract class A{

    abstract static class Builder<T extends Builder<T>>{

        public Builder(){
        }

        public T prp1(int prp1){
            this.prp1 = prp1;
            return self();
        }

        abstract A build();

        protected abstract T self();
    }

    private A(Builder<?> builder){
        // copio los atributos de A
    }

}

public class B extends A{

    public static class Builder extends A.Builder<Builder>{

        public Builder(){
        }

        @Override public B build(){
            return new B(this)
        }

        public T prp2(int prp2){
            this.prp2 = prp2;
            return self();
        }

        @Override protected Builder self(){ return this;}
    }

    private B(Builder builder){
        // copio los atributos de B
    }
}
Enter fullscreen mode Exit fullscreen mode

El "truco" se encuentra en el Generic de la clase A: Builder<T extends Builder<T> > así como en el métodoself. Este es usado internamente por el builder para permitir al compilador poder concatenar las llamadas

A a = new B.Builder().prp1(1).prp2(2).build

Top comments (0)