Builders

  • marzo, 03 2022
  • Jorge Aguilera
  • 04:00
  • java

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 te encuentras creando métodos similares a los que le vas añadiendo un parámetro cada vez se le llama "telescoping constructor"

telescoping constructor
class Person{
    public static Person newInstance(){
        return new Person();
    }
    public static Person newInstance(String nombre){
        return new Person(nombre);
    }
    public static Person newInstance(String nombre, int edad){
        return new Person(nombre, edad...);
    }
    public static Person newInstance(String nombre, int edad, Date nacimiento){
        return new Person(nombre, edad...);
    }
    public static Person newInstance(String nombre, int edad, Date nacimiento, Ciudad ciudad){
        return new Person(nombre, edad...);
    }
    ...
}

Otra forma típica de inicializar objetos cuando el número de parámetros es numeroso es aplicando la aproximación de JavaBeans creando métodos get/set para cada propiedad de la instancia:

java beans
class Person{
    String nombre;
    int edad;
    Ciudad ciudad;
    ... getters/setters
}

Person p = new Person();
p.setNombre("nombre");
p.setEdad(12);
p.setXXX

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 Person{

    private final String nombre;

    public int getNombre() { return nombre;}
    // NO tenemos metodo setNombre

    public static class Builder{
        private final String nombre;

        public Builder(){}

        public Builder nombre(String nombre){ nombre = nombre; return this;}
        ...

        public Person build(){
            // validar que tenemos todos los parametros necesarios
            return new Person(this);
        }
    }
    private Person(Builder builder){
        nombre = builder.nombre;
        ...
    }
}

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

Person a = new Person.Builder().nombre("pepe").build();
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 Person{

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

        public Builder(){
        }

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

        abstract Person build();

        protected abstract T self();
    }

    private Person(Builder<?> builder){
        this.nombre = builder.nombre;
    }
    String nombre;
}
public class Cliente extends Person{

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

        public Builder(){
        }

        @Override public CLiente build(){
            return new Cliente(this)
        }

        int codigo;

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

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

    int codigo;

    private Cliente(Builder builder){
        this.codigo = builder.codigo;
    }
}

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

Person person = new Cliente.Builder().nombre("pepe").codigo(2).build()

Este texto ha sido escrito por un humano

This post was written by a human

2019 - 2024 | Mixed with Bootstrap | Baked with JBake v2.6.7