Java 52: The Builder Pattern & Fluent APIs
easy⏱ 5 mincoursejava
¿Por qué no constructores telescópicos?
Con 8 campos (algunos opcionales), o escribes una docena de constructores sobrecargados (telescópicos — ilegibles: new Req(url, GET, null, 30, null, true, ...)) o un JavaBean con setters (mutable, usable a medio construir). El Builder evita ambos: cada opción es una llamada fluida con nombre, el objeto se construye una vez y se congela, y no puedes observarlo en estado inconsistente.
HttpRequest req = HttpRequest.builder("https://api")
.method("POST")
.header("Accept", "json")
.timeout(5000)
.build(); // validated, immutable
Encadenado fluido: return this
Cada método del builder muta el estado del propio builder y hace return this, así las llamadas se encadenan. El build() final corre la validación (lanza ante campos requeridos faltantes/inválidos), aplica defaults a los opcionales no fijados y construye el producto inmutable pasando el estado acumulado a un constructor privado. La validación vive en un solo lugar — build() — no dispersa por los setters.
Inmutabilidad del producto
El objeto construido expone solo campos final y getters — sin setters. El builder, no el producto, es mutable. Para campos de colección, copia defensivamente en build() (List.copyOf(...)) para que una mutación posterior de la lista del llamador no se filtre al objeto inmutable. Un record es un gran tipo de producto inmutable, con el Builder como su helper estático anidado.
Construye un builder de HttpRequest inmutable
Crea HttpRequestBuilder con method, header(k,v) (acumula), body y timeout fluidos. build() debe: (1) lanzar si la URL está en blanco; (2) poner por defecto método 'GET' y timeout 30000 si no se fijaron; (3) copiar defensivamente el mapa de headers; (4) devolver un HttpRequest inmutable. Construye una request POST válida e imprímela, luego captura el error de construir con URL en blanco.
Los builders por etapas exigen campos requeridos
Un builder por etapas (step) devuelve una interfaz distinta tras cada llamada requerida, así el compilador fuerza el orden: builder().url(x).method(y).build() — literalmente no puedes llamar build() hasta completar los pasos requeridos. Es más boilerplate pero convierte la validación de runtime en garantías de compilación. Úsalo para APIs donde el mal uso es costoso.
Quiz: Builder vs setters
¿Por qué preferir un Builder sobre un objeto mutable con setters? El producto es inmutable y siempre totalmente validado — un bean con setters puede observarse a medio inicializar y mutarse después, anulando thread-safety e invariantes. El Builder concentra la validación en build() y produce un objeto seguro de compartir entre hilos sin sincronización.