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
- ¿Puedes implementar un Builder que valide propiedades requeridas usando TypeScript?
- ¿Sabes usar métodos que retornen
thispara permitir encadenamiento fluido? - ¿Puedes aplicar tipos condicionales o uniones para manejar variantes en la construcción?
- ¿Entiendes cómo evitar efectos secundarios manteniendo la inmutabilidad?
- ¿Eres capaz de dividir un Builder complejo en sub-builders para mejorar mantenibilidad?
- ¿Puedes integrar el patrón Builder en una arquitectura frontend existente?
- ¿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:
- Define una interfaz
ChartConfigcon propiedades comotype(line, bar, pie),data(array de números),labels(array de strings),colors(array opcional de strings), ytitle(string opcional). - Crea una clase
ChartBuilderque use tipos genéricos para garantizar quetype,data, ylabelssean configurados antes de llamar abuild(). - Implementa métodos como
setType(),setData(),setLabels(),setColors(), ysetTitle(), que retornenthispara encadenamiento. - En el método
build(), valida quedataylabelstengan la misma longitud, y quecolors(si se proporciona) coincida con el número de elementos endata. - Escribe un ejemplo de uso que cree un gráfico de barras con datos de ventas mensuales, incluyendo colores personalizados y un título.
- 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.