Saltar al contenido

Lección 6 de 20 · Lectura y práctica

Patrón Builder para Configuraciones Complejas

Concepto claveEl Patrón Builder es un patrón de diseño creacional que permite construir objetos complejos paso a paso, separando la construcción de su representación. En TypeScript, este patrón es especialmente valioso cuando.

Del concepto a la práctica

Taller de esta lección

El Builder resulta útil cuando una configuración tiene una secuencia clara y varias opciones. En vez de aceptar un objeto parcial y fallar tarde, puede guardar valores progresivamente y validar al construir.

Ejemplo resuelto

ts
type Config = { name: string; retries: number; cache: boolean };
class ConfigBuilder {
  private value: Partial<Config> = {};
  name(name: string) { this.value.name = name; return this; }
  retries(n: number) {
    if (!Number.isSafeInteger(n) || n < 0) throw new Error("retries inválidos");
    this.value.retries = n; return this;
  }
  cache(on: boolean) { this.value.cache = on; return this; }
  build(): Config {
    const { name, retries, cache } = this.value;
    if (!name?.trim() || retries === undefined || cache === undefined) throw new Error("config incompleta");
    return { name: name.trim(), retries, cache };
  }
}

Pruébalo

  1. Construye local, 2 reintentos y caché activa.

  2. Ejecuta build() y muestra el objeto.

  3. Intenta construir sin nombre.

Comprueba tu resultado

La configuración válida debe ser {name:"local",retries:2,cache:true} y la incompleta debe lanzar config incompleta.

Error frecuente: aceptar reintentos negativos. Corrígelo comprobando n >= 0 antes de guardarlo.

Esta versión valida en ejecución: no es un Builder de estados que impida build al compilar. El objeto devuelto es nuevo, así que una llamada posterior retries(3) no altera la configuración anterior. Rechaza también NaN, Infinity,1.5 y nombre de espacios; acepta retries0 y cachefalse. Escribe las llamadas de demostración debajo de la clase y captura el error esperado sin interrumpir las pruebas siguientes.

Diagrama de decisión con ramas Sí y No y un bucle que procesa cada elemento de una lista.
CursaloMapa de práctica
Cómo avanza un programa

Una condición decide qué camino ejecuta el programa. Un bucle repite una operación mientras queden elementos, y termina cuando la lista se agota. Separar ambas ideas ayuda a seguir el flujo y comprobar casos límite.

Ver la infografía en tamaño completo ↗
Fuentes para profundizar

Concepto clave

El Patrón Builder es un patrón de diseño creacional que permite construir objetos complejos paso a paso, separando la construcción de su representación. En TypeScript, este patrón es especialmente valioso cuando trabajas con configuraciones que tienen múltiples parámetros opcionales, dependencias complejas o validaciones específicas.

Imagina que estás construyendo una casa: no la construyes de una sola vez, sino que sigues un proceso paso a paso (cimientos, paredes, techo, instalaciones). El Builder actúa como el arquitecto que coordina este proceso, permitiéndote personalizar cada etapa sin exponer la complejidad interna. En el desarrollo frontend, esto se aplica a componentes UI complejos, configuraciones de API, o inicialización de librerías de terceros.

"El Builder transforma configuraciones caóticas en flujos controlados y tipados."

Cómo funciona en la práctica

Implementar el Builder en TypeScript implica crear una clase o función que encapsule la lógica de construcción. Usualmente incluye métodos para configurar cada propiedad y un método final que retorna el objeto construido. La clave está en usar tipos genéricos para garantizar que solo se pueda llamar al método final cuando todas las propiedades requeridas estén configuradas.

Paso a paso: 1) Define una interfaz para el objeto final. 2) Crea una clase Builder con métodos que retornen this para encadenamiento. 3) Usa un estado interno para acumular configuraciones. 4) Implementa un método build() que valide y retorne el objeto. Aquí un ejemplo básico:

interface ConfiguracionAPI {
  url: string;
  timeout: number;
  headers?: Record;
}

class APIBuilder {
  private config: Partial = {};

  setUrl(url: string): this {
    this.config.url = url;
    return this;
  }

  setTimeout(timeout: number): this {
    this.config.timeout = timeout;
    return this;
  }

  setHeaders(headers: Record): this {
    this.config.headers = headers;
    return this;
  }

  build(): ConfiguracionAPI {
    if (!this.config.url || !this.config.timeout) {
      throw new Error('URL y timeout son requeridos');
    }
    return this.config as ConfiguracionAPI;
  }
}

// Uso:
const config = new APIBuilder()
  .setUrl('https://api.ejemplo.com')
  .setTimeout(5000)
  .setHeaders({ 'Authorization': 'Bearer token' })
  .build();

Caso de estudio

Considera un sistema de notificaciones para una aplicación de e-commerce. Necesitas configurar notificaciones que pueden ser de tipo email, SMS, o push, con diferentes prioridades, plantillas, y destinatarios. Usar un Builder asegura que cada notificación se construya correctamente, validando reglas de negocio como formatos de email o límites de caracteres.

Implementación avanzada con tipos condicionales:

type TipoNotificacion = 'email' | 'sms' | 'push';

interface NotificacionBase {
  tipo: TipoNotificacion;
  prioridad: 'alta' | 'media' | 'baja';
}

interface NotificacionEmail extends NotificacionBase {
  tipo: 'email';
  asunto: string;
  cuerpoHTML: string;
}

interface NotificacionSMS extends NotificacionBase {
  tipo: 'sms';
  mensaje: string;
  caracteresMaximos: number;
}

class NotificacionBuilder {
  private tipo: T;
  private datos: Partial = {};

  constructor(tipo: T) {
    this.tipo = tipo;
  }

  setPrioridad(prioridad: 'alta' | 'media' | 'baja'): this {
    this.datos.prioridad = prioridad;
    return this;
  }

  // Métodos específicos por tipo usando sobrecarga o uniones
  build(): NotificacionEmail | NotificacionSMS {
    // Lógica de construcción y validación
    return { tipo: this.tipo, ...this.datos } as any;
  }
}

Este enfoque permite crear notificaciones tipadas de forma segura, evitando errores en tiempo de ejecución.

Errores comunes

  • No validar en el método build: Construir objetos incompletos puede llevar a bugs difíciles de rastrear. Siempre incluye validaciones para propiedades requeridas.
  • Abusar del encadenamiento: Si tienes más de 5-7 métodos de configuración, considera dividir el Builder en sub-builders para mantener la legibilidad.
  • Ignorar inmutabilidad Modificar el estado interno después de llamar a build() puede causar efectos secundarios. Asegúrate de que el Builder no retenga referencias al objeto construido.
  • No usar tipos genéricos cuando son necesarios: En casos complejos, los genéricos permiten garantizar seguridad de tipos a lo largo del flujo de construcción.
  • Olvidar resetear el estado: Si reutilizas la misma instancia de Builder, limpia el estado interno entre construcciones para evitar contaminación cruzada.

Checklist de dominio

  1. ¿Puedes implementar un Builder que valide propiedades requeridas usando TypeScript?
  2. ¿Sabes usar métodos que retornen this para permitir encadenamiento fluido?
  3. ¿Puedes aplicar tipos condicionales o uniones para manejar variantes en la construcción?
  4. ¿Entiendes cómo evitar efectos secundarios manteniendo la inmutabilidad?
  5. ¿Eres capaz de dividir un Builder complejo en sub-builders para mejorar mantenibilidad?
  6. ¿Puedes integrar el patrón Builder en una arquitectura frontend existente?
  7. ¿Sabes medir el impacto en performance al usar Builders vs. constructores simples?

Implementa un Builder para Configuraciones de Gráficos en una Librería de Visualización

En este ejercicio, crearás un Builder en TypeScript para configurar gráficos en una librería de visualización de datos. Sigue estos pasos:

  1. Define una interfaz ChartConfig con propiedades como type (line, bar, pie), data (array de números), labels (array de strings), colors (array opcional de strings), y title (string opcional).
  2. Crea una clase ChartBuilder que use tipos genéricos para garantizar que type, data, y labels sean configurados antes de llamar a build().
  3. Implementa métodos como setType(), setData(), setLabels(), setColors(), y setTitle(), que retornen this para encadenamiento.
  4. En el método build(), valida que data y labels tengan la misma longitud, y que colors (si se proporciona) coincida con el número de elementos en data.
  5. Escribe un ejemplo de uso que cree un gráfico de barras con datos de ventas mensuales, incluyendo colores personalizados y un título.
Pistas
  • Usa tipos genéricos para restringir los métodos disponibles basados en las propiedades ya configuradas.
  • Considera usar un estado interno con tipos parciales para acumular la configuración paso a paso.
  • Para la validación en build(), puedes usar condicionales y lanzar errores descriptivos si algo falta o es inválido.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Sigue con TypeScript Avanzado: Patrones Genéricos y Tipado Profundo para Arquitecturas Complejas

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.