Ejemplo del patrón de diseño Builder

Escrito por el , actualizado el .
blog-stack java planeta-codigo programacion
Comentarios

Construir objetos es una tarea básica en los lenguajes orientados a objetos. En Java, las instancias de una clase se crean con la palabra clave reservada new y un método especial llamado constructor. Al diseñar una clase debemos tener algunas cuestiones para evitar varios constructores telescópicos, evitar constructores que son combinación de varios argumentos opcionales y permitir obtener instancias de objetos con estado válido. Si se nos presentan estas situaciones podemos usar el patrón de diseño Builder que consiste en básicamente en una clase especializada en construir instancias de otra clase que podemos hacer usable con una API fluida y alguna cosa más deseable que explico en el artículo.

Java

Al escribir los métodos constructores de instancias de una clase puede ocurrirnos que algunos de ellos tienen una lista larga de argumentos (cuatro o más parámetros puede considerarse larga) o el caso de que otros algunos argumentos son opcionales. En el caso de una lista larga de argumentos algunos puedan tomar valores por defecto creando métodos telescópicos (donde hay varios constructores y cada uno solo añade un nuevo argumento al anterior), en el caso de argumentos opcionales nos obliga a crear un constructor por cada combinación de argumentos, peor aún, ambas cosas se pueden producir a la vez.

Por ejemplo, supongamos que tenemos una entidad de dominio Usuario en la que el correo electrónico es requerido siendo opcionales su nombre, apellidos teléfono o dirección. Sin usar el patrón de diseño Builder probablemente tendríamos los siguientes constructores o tener solo el último de ellos y en los no necesarios usar como valor del argumento null.

1
2
3
4
5
6
7
8
9
// Contructores con el problema de ser telescópicos y ser mútiples por combinación de parámetros
public Usuario(String email)
public Usuario(String email, String nombre, String apellidos)
public Usuario(String email, String telefono)
public Usuario(String email, String direccion)
public Usuario(String email, String nombre, String apellidos, String telefono)
public Usuario(String email, String nombre, String apellidos, String direccion)
public Usuario(String email, String telefono, String direccion)
public Usuario(String email, String nombre, String apellidos, String telefono, String direccion)

Como vemos no son pocos constructores debido a las combinaciones de los parámetros opcionales, esta forma requiere una buena cantidad de líneas de código y si decidiesemos escribir solo el constructor con todos los parámetros al usarlo tendremos dificultades para saber a que argumento responde cada variable y probablemente deberemos consultar la firma del constructor para saber que lugar ocupa cada argumento, esto dificulta la legibilidad.

1
2
// Uso de un contructor, ¿a que argumento corresponde cada dato?
new Usuario("nombre.apellido@gmail.com", "Nombre", "Apellido", "555123456", "c\\ Rue el Percebe 13")

En este caso solo hay tres argumentos opcionales si hubiera más el número de combinaciones y por tanto de constructores aumentaría considerablemente. Puede que en vez de usar constructores usemos un método set de JavaBean de forma que tengamos un solo constructor y múltiples métodos set o un constructor con los argumentos requeridos y un set por cada argumento opcional.

1
2
3
4
5
6
7
// Siguiendo las convenciones de los JavaBeans la clase Usuario deja de ser inmutable
public Usuario()
public Usuario(String email)
public setEmail(String email)
public setNombre(String nombre, String apellidos)
public setTelefono(String telefono)
public setDireccion(String direccion)

Sin embargo, esta solución aunque permite reducir el número de constructores también tiene problemas, uno de ellos es que el constructor y los set no obligan a crear un objeto con estado consistente o válido, otro es que usando los set de los JavaBean nos impide hacer el objeto inmutable, si no es devolviendo una nueva instancia, que con las nuevas características funcionales añadidas en Java 8 y en la programación cocurrente es deseable.

La solución a los constructores telescópicos y combinación de argumentos es usar el patrón de diseño Builder. Por ejemplo, empleando el mismo caso que los anteriores de la siguiente forma.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package io.github.picodotdev.pattern.builder;

public class Usuario {
  
    private String email;
    private String nombre;
    private String apellidos;
    private String telefono;
    private String direccion;
  
    private Usuario() {
    }
  
    Usuario(UsuarioBuilder builder) {
        if (builder.getEmail() == null) {
            throw new IllegalArgumentException("email es requerido");
        }
        this.email = builder.getEmail();
        this.nombre = builder.getNombre();
        this.apellidos = builder.getApellidos();
        this.telefono = builder.getTelefono();
        this.direccion = builder.getDireccion();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package io.github.picodotdev.pattern.builder;

public class UsuarioBuilder {

    private String email;
    private String nombre;
    private String apellidos;
    private String telefono;
    private String direccion;

    public UsuarioBuilder() {
    }

    public UsuarioBuilder email(String email) {
        this.email = email;
        return this;
    }

    public UsuarioBuilder nombre(String nombre, String apellidos) {
        this.nombre = nombre;
        this.apellidos = apellidos;
        return this;
    }

    public UsuarioBuilder telefono(String telefono) {
        this.telefono = telefono;
        return this;
    }

    public UsuarioBuilder direccion(String direccion) {
        this.direccion = direccion;
        return this;
    }

    public Usuario build() {
        return new Usuario(this);
    }
    
    // Getters
    public String getEmail() {
        return email;
    };
    
    public String getNombre() {
        return nombre;
    };
    
    public String getApellidos() {
        return apellidos;
    };
    
    public String getTelefono() {
        return telefono;
    };
    
    public String getDireccion() {
        return direccion;
    };
}

Su uso sería de la siguiente manera algo más autoexplicativa y legible que la opción de usar constructores.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package io.github.picodotdev.pattern.builder;

public class Main {
  
  public static void main(String[] args) {
    Usuario usuario = new UsuarioBuilder()
        .email("nombre.apellido@gmail.com")
        .nombre("Nombre", "Apellido")
        .telefono("555123456")
        .direccion("c\\ Rue el Percebe 13").build();   
  }
}

La instancia de la clase UsuarioBuilder en su uso recoge los datos usando una API fluida, el método build es el que construye la instancia del usuario mediante el constructor con visibilidad de paquete en el que se valida que los datos al construir el objeto Usuario sean válidos, en este caso que el email es requerido.

En el libro Effective Java en el Item #2 se comenta más detalladamente este patrón junto a otra buena colección de cosas sobre los constructores y más cosas sobre Java, es uno en mi lista de 8+ libros recomendables para mejorar como programadores.

En el apartado de referencia puedes encontrar más artículos que he escrito sobre otros patrones de diseño.