Cuando hablamos de Clean Code, una de las reglas básicas es utilizar nombres descriptivos que declaren la intención. Esto es trivial en métodos y variables, pero los constructores tienen un nombre fijo y único, y cuando empiezan a aparecer sobrecargas, esa imposibilidad de nombrarlos de forma expresiva penaliza la claridad y legibilidad del código. Es ahí donde Robert C. Martin sugiere en su libro Clean Code el uso de métodos de factoría y constructores privados.

El problema con la sobrecarga de constructores

A priori, la sobrecarga de constructores parece una solución práctica que nos permite declarar tantos como necesitemos y haciendo cualquier combinación de atributos, según las necesidades del código.

1
2
3
new User("Juan");
new User("Juan", 35);
new User("Juan", 35, true);

Pero esta sobrecarga escala muy mal y penaliza la legibilidad y comprensión. No sabemos qué representa cada parámetro, son ambiguos y amplían la posibilidad de error aunque el programador invierta tiempo en repasar su definición antes de cada uso.

1
2
3
4
5
6
public class User {
    public User(String name) { ... }
    public User(String name, int age) { ... }
    public User(String name, int age, boolean isAdmin) { ... }
    public User(String name, int age, boolean isAdmin, LocalDate createdAt) { ... }
}

Uso de métodos factoría

Los métodos de factoría (factory methods) son métodos estáticos con nombre, ayudan a resolver esta ambigüedad y expresar y definir de manera más clara qué se está creando. Para evitar totalmente la ambigüedad, los constructores pueden ser privados.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class User {

    private final String name;
    private final int age;

    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static User withName(String name) {
        return new User(name, 0);
    }

    public static User withNameAndAge(String name, int age) {
        return new User(name, age);
    }
}
1
2
User u1 = User.withName("Juan");
User u2 = User.withNameAndAge("Juan", 35);

Ventajas clave

  1. Claridad: los nombres expresan la intención.
  2. Flexibilidad: se pueden devolver subtipos, instancias cacheadas, objetos inmutables, etc.
  3. Ocultan la complejidad: se centraliza la lógica de creación en un único punto.
  4. Evolución más sencilla: se puede ampliar el comportamiento sin romper constructores existentes.

Cuando es mejor usar un Builder

Si la clase cuenta con muchos parámetros, y/o muchos de ellos son opcionales y, además, queremos incluir una lógica de validación compleja y configuraciones combinatorias (valores de un atributo condicionados al valor de otro), los Builder pasan a ser la solución más potente e indicada.

Conclusiones

Para casos simples, un constructor normal está bien. Pero cuando tenemos varias formas de crear el mismo objeto o una lógica de inicialización con cierta complejidad, el uso de métodos de factoría o Builders se posicionan como una solución más clara. El objetivo final es que el código sea fácil de leer, más difícil de romper y más sencillo de extender.