Ejemplo del patrón de diseño Builder

Escrito por el , actualizado el .
java planeta-codigo programacion
Enlace permanente 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)
Usuario-1.java

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 decidiésemos 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")
Usuario-2.java

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)
Usuario-3.java

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 concurrente es deseable.

El patrón de diseño Builder

El patrón de diseño Builder es un patrón de diseño clasificado en los creacionales que se encarga de la creación de instancias de clases. Sus ventajas son que solucionan el problema de los constructores telescópicos y combinación de argumentos es usar el patrón de diseño Builder, además permite crear objetos complejos de forma flexible en varios pasos con propiedades opcionales.

Al igual que el patrón de diseño Factory se encarga de crear instancias de una clase, sin embargo, tiene algunas diferencias como que el patrón Factory crea las instancias en un único paso cuando el Builder puede crear las instancias en varios pasos, el patrón Builder es más complejo pero más flexible. Otra diferencia es que el patrón de diseño Builder tiene estado y en el Factory no siempre es necesario, esto hace que la instancia de clase Builder no se pueda compartir ni utilizar para crear otras instancias.

Diagrama de clases del patrón de diseño Builder

Diagrama de clases del patrón de diseño Builder

Ejemplo del patrón de diseño Builder

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();
    }
}
Usuario.java
 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;
    };
}
UsuarioBuilder.java

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();   
  }
}
Main.java

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.


Comparte el artículo: