Abby's Digital Cafe

Patrón creaciónal - Builder

Introducción al patrón Builder

El patrón Builder es un patrón creacional pensado para construir objetos complejos paso a paso, evitando constructores telescópicos y mejorando la legibilidad y mantenibilidad. En lugar de pasar 10 parámetros opcionales a un constructor, se utiliza un objeto "builder" que va configurando la instancia y al final ejecuta un método build() que devuelve el objeto final.

¿Cuándo usar Builder?

  • Cuando un objeto tiene muchos parámetros opcionales o combinaciones posibles.
  • Cuando quieres separar la construcción del objeto de su representación.
  • Cuando necesitas crear variaciones de un objeto de forma legible y testeable.

Pasos para implementar Builder

  1. Identificar la clase del objeto complejo (p. ej. Order, HttpRequest, UserProfile).
  2. Definir las propiedades inmutables o configurables.
  3. Crear una clase Builder que exponga métodos encadenables (fluent) para cada propiedad opcional.
  4. Validar en build() las combinaciones requeridas y devolver la instancia construida.
  5. Preferir objetos inmutables para evitar efectos secundarios después de construido el objeto.

Ejemplo práctico en TypeScript

A continuación un ejemplo de un Email construido con Builder. Muestra cómo manejar campos obligatorios, opcionales y validaciones simples.

[typescript]
class Email { readonly to: string; readonly subject: string; readonly body: string; readonly cc?: string[]; readonly attachments?: string[]; private constructor(builder: EmailBuilder) { this.to = builder.to; this.subject = builder.subject; this.body = builder.body; this.cc = builder.cc; this.attachments = builder.attachments; } static get Builder() { return new EmailBuilder(); } } class EmailBuilder { to!: string; // obligatorio subject: string = ''; body: string = ''; cc?: string[]; attachments?: string[]; withTo(to: string) { this.to = to; return this; } withSubject(subject: string) { this.subject = subject; return this; } withBody(body: string) { this.body = body; return this; } withCc(cc: string[]) { this.cc = cc; return this; } withAttachments(attachments: string[]) { this.attachments = attachments; return this; } build(): Email { if (!this.to) throw new Error('Email <span class="token string">"to" es obligatorio'</span>); return new Email(this); } } // Uso const email = Email.Builder .withTo('[email protected]') .withSubject('Deploy') .withBody('Se desplegó en producción') .build();

Ejemplo práctico en Java

En Java solemos usar la clase estática interna Builder para que el uso sea más expresivo y encapsulado.

[java]
public class Pizza { private final String size; // obligatorio private final boolean cheese; private final boolean pepperoni; private final boolean bacon; private Pizza(Builder builder) { this.size = builder.size; this.cheese = builder.cheese; this.pepperoni = builder.pepperoni; this.bacon = builder.bacon; } public static class Builder { private final String size; private boolean cheese = false; private boolean pepperoni = false; private boolean bacon = false; public Builder(String size) { this.size = size; } public Builder cheese(boolean value) { this.cheese = value; return this; } public Builder pepperoni(boolean value) { this.pepperoni = value; return this; } public Builder bacon(boolean value) { this.bacon = value; return this; } public Pizza build() { // ejemplo de validación if (!"small".equals(size) && !"medium".equals(size) && !"large".equals(size)) { throw new IllegalStateException("size debe ser small|medium|large"); } return new Pizza(this); } } } // Uso Pizza pizza = new Pizza.Builder("medium") .cheese(true) .pepperoni(true) .build();

Ventajas y consideraciones

  • Mejora la legibilidad respecto a constructores con muchos parámetros.
  • Facilita la creación de objetos inmutables.
  • Permite validaciones centralizadas en build().
  • Puede producir clases adicionales (algo de boilerplate). En Java se suele reducir con Lombok (@Builder) — si tu proyecto lo permite.

¿Qué diferencia al Builder de un Factory?

Factory abstrae la creación de objetos (puede devolver subclases) y encapsula la lógica de elección del tipo; Builder se centra en construir una única estructura compleja paso a paso. Ambos pueden combinarse.

¿Cómo pruebo objetos creados con Builder?

Usa builders en tus tests para crear fixtures rápidamente (arrange). Puedes implementar builders específicos para pruebas que expongan defaults útiles y solo modifiquen lo necesario.

¿El Builder afecta el rendimiento?

La sobrecarga es mínima: creación de objetos extra (el builder) y llamadas encadenadas. En la mayoría de aplicaciones esa diferencia es despreciable frente a claridad y mantenibilidad.

¿Puedo reutilizar un builder para construir varios objetos?

Sí, pero ten cuidado con el estado mutado; preferible crear uno nuevo o clonar el estado inicial.

¿Cómo podría manejar parámetros obligatorios?

Pasarlos al constructor del Builder (ver ejemplo Java) o validar en build().

Comentarios