Saltar al contenidoJMRG
Todas las entradas

Patrón Builder: Construcción Paso a Paso de Objetos Complejos

design-patternscreationalbuilder

Intención

El patrón Builder separa la construcción de un objeto complejo de su representación, permitiendo que el mismo proceso de construcción cree diferentes representaciones. Es especialmente útil cuando un objeto tiene muchos parámetros opcionales.

El Problema

¿Has visto constructores como este?

// Constructor telescópico - difícil de usar y mantener
const user = new User(
  'John',
  'Doe',
  '[email protected]',
  true,
  false,
  'premium',
  null,
  'dark',
  ['es', 'en']
);

Sin mirar la documentación, ¿qué significa cada parámetro? ¿Qué pasa si solo quieres establecer el tema sin modificar el plan?

La Solución: Builder

const user = new UserBuilder()
  .withName('John', 'Doe')
  .withEmail('[email protected]')
  .asVerified()
  .withPlan('premium')
  .withTheme('dark')
  .withLanguages(['es', 'en'])
  .build();

Cada método es auto-documentado. Solo estableces lo que necesitas.

Diagrama

classDiagram
    class Builder {
        <<interface>>
        +setPartA()
        +setPartB()
        +build(): Product
    }

    class ConcreteBuilder {
        -product: Product
        +setPartA()
        +setPartB()
        +build(): Product
    }

    class Director {
        -builder: Builder
        +construct()
    }

    class Product {
        +partA
        +partB
    }

    Builder <|.. ConcreteBuilder
    Director --> Builder
    ConcreteBuilder --> Product

Implementación TypeScript

// Producto: Petición HTTP
interface HttpRequest {
  method: string;
  url: string;
  headers: Record<string, string>;
  body?: unknown;
  timeout: number;
  retries: number;
}

// Builder con API fluida
class HttpRequestBuilder {
  private request: Partial<HttpRequest> = {
    headers: {},
    timeout: 30000,
    retries: 0,
  };

  get(url: string): this {
    this.request.method = 'GET';
    this.request.url = url;
    return this;
  }

  post(url: string): this {
    this.request.method = 'POST';
    this.request.url = url;
    return this;
  }

  withHeader(key: string, value: string): this {
    this.request.headers![key] = value;
    return this;
  }

  withAuth(token: string): this {
    return this.withHeader('Authorization', `Bearer ${token}`);
  }

  withBody(body: unknown): this {
    this.request.body = body;
    return this.withHeader('Content-Type', 'application/json');
  }

  withTimeout(ms: number): this {
    this.request.timeout = ms;
    return this;
  }

  withRetries(count: number): this {
    this.request.retries = count;
    return this;
  }

  build(): HttpRequest {
    if (!this.request.method || !this.request.url) {
      throw new Error('Method and URL are required');
    }
    return this.request as HttpRequest;
  }
}

// Uso
const request = new HttpRequestBuilder()
  .post('https://api.example.com/users')
  .withAuth('my-token')
  .withBody({ name: 'John' })
  .withTimeout(5000)
  .withRetries(3)
  .build();

Director (Opcional)

El Director encapsula configuraciones comunes:

class RequestDirector {
  static createAuthenticatedPost(url: string, token: string, body: unknown) {
    return new HttpRequestBuilder()
      .post(url)
      .withAuth(token)
      .withBody(body)
      .withTimeout(10000)
      .withRetries(2)
      .build();
  }

  static createHealthCheck(url: string) {
    return new HttpRequestBuilder()
      .get(url)
      .withTimeout(3000)
      .build();
  }
}

Cuándo Usar

Situación Recomendación
Objeto con muchos parámetros opcionales ✅ Recomendado
Configuraciones complejas ✅ Recomendado
Construcción paso a paso con validación ✅ Recomendado
Objeto simple con pocos parámetros ❌ Excesivo

Conclusión

El patrón Builder es tu mejor aliado para construir objetos complejos de forma legible y mantenible.