Problema
Construir objetos complejos paso a paso, evitando constructores con muchos parámetros y permitiendo diferentes representaciones del mismo objeto.
Propósito
Separar la construcción de un objeto complejo de su representación, permitiendo que el mismo proceso de construcción pueda crear diferentes representaciones. Facilita la creación de objetos inmutables complejos.
Casos de uso comunes
- Configuraciones complejas (base de datos, APIs, servidores)
- Objetos con muchos parámetros opcionales
- Consultas SQL dinámicas
- Objetos inmutables complejos
- Builders de UI (formularios, ventanas)
- Configuraciones de testing
¿Quién es quién en Builder?
Actor | Lo que realmente es | Ejemplo | Analogía |
---|---|---|---|
Builder | Acumula parámetros y valida antes de crear | DatabaseConfigBuilder - configura paso a paso |
Arquitecto (diseña casa paso a paso) |
Product | Objeto final complejo e inmutable | DatabaseConfig - configuración completa |
Casa terminada (compleja, muchas opciones) |
Director | Coordinador opcional que conoce pasos | Cliente que dice “configuración de producción” | Cliente (“casa familiar” vs “casa de lujo”) |
Fluent Interface | API encadenada para facilidad de uso | .host().port().database().build() |
Conversación natural (“3 habitaciones, 2 baños”) |
Diagrama
classDiagram
namespace BuilderPattern {
class Client {
+main()
}
class Builder {
-partA: String
-partB: String
-partC: String
+setPartA(value) Builder
+setPartB(value) Builder
+setPartC(value) Builder
+build() Product
}
class Product {
-partA: String
-partB: String
-partC: String
+Product(builder)
}
}
Client --> Builder
Client --> Product
Builder --> Product : builds
Ejemplo práctico: Configuración de Base de Datos
classDiagram
namespace DatabaseExample {
class Client {
+main()
}
class DatabaseConfigBuilder {
-host: String
-port: Integer
-database: String
-username: String
-password: String
-maxConnections: Integer
-timeout: Integer
-ssl: Boolean
-retryAttempts: Integer
-poolSize: Integer
+host(host) DatabaseConfigBuilder
+port(port) DatabaseConfigBuilder
+database(name) DatabaseConfigBuilder
+credentials(user, pass) DatabaseConfigBuilder
+maxConnections(max) DatabaseConfigBuilder
+timeout(seconds) DatabaseConfigBuilder
+enableSSL() DatabaseConfigBuilder
+retryAttempts(attempts) DatabaseConfigBuilder
+poolSize(size) DatabaseConfigBuilder
+build() DatabaseConfig
}
class DatabaseConfig {
-host: String
-port: Integer
-database: String
-username: String
-password: String
-maxConnections: Integer
-timeout: Integer
-ssl: Boolean
-retryAttempts: Integer
-poolSize: Integer
+connect() Connection
+isValid() Boolean
}
}
Client --> DatabaseConfigBuilder
Client --> DatabaseConfig
DatabaseConfigBuilder --> DatabaseConfig : builds
Flujo de construcción
sequenceDiagram
participant Client
participant DatabaseConfigBuilder
participant DatabaseConfig
Note over Client: 1. Cliente configura solo lo que necesita
Client->>DatabaseConfigBuilder: new DatabaseConfigBuilder()
Client->>DatabaseConfigBuilder: host("localhost")
DatabaseConfigBuilder-->>Client: this
Client->>DatabaseConfigBuilder: port(5432)
DatabaseConfigBuilder-->>Client: this
Client->>DatabaseConfigBuilder: database("myapp")
DatabaseConfigBuilder-->>Client: this
Client->>DatabaseConfigBuilder: credentials("user", "pass")
DatabaseConfigBuilder-->>Client: this
Client->>DatabaseConfigBuilder: maxConnections(20)
DatabaseConfigBuilder-->>Client: this
Client->>DatabaseConfigBuilder: enableSSL()
DatabaseConfigBuilder-->>Client: this
Note over Client: 2. Builder aplica defaults y valida
Client->>DatabaseConfigBuilder: build()
DatabaseConfigBuilder->>DatabaseConfigBuilder: applyDefaults()
DatabaseConfigBuilder->>DatabaseConfigBuilder: validate()
DatabaseConfigBuilder->>DatabaseConfig: new DatabaseConfig(this)
DatabaseConfig-->>DatabaseConfigBuilder: config instance
DatabaseConfigBuilder-->>Client: DatabaseConfig
Note over Client: 3. Cliente usa la configuración
Client->>DatabaseConfig: connect()
Ventajas
- Legibilidad: Código más claro que constructores con muchos parámetros
- Flexibilidad: Permite diferentes representaciones del mismo objeto
- Inmutabilidad: Facilita la creación de objetos inmutables
- Validación: Centraliza validaciones antes de crear el objeto
- Fluent Interface: API más expresiva y fácil de usar
Desventajas
- Complejidad: Introduce más código y clases
- Memoria: Crea objetos intermedios durante la construcción
- Duplicación: Puede duplicar campos entre builder y producto
- Overhead: Más costoso que constructores simples
Cuándo usar
- Objetos con muchos parámetros (más de 4-5)
- Muchos parámetros opcionales
- Necesitas objetos inmutables complejos
- Quieres validar antes de crear el objeto
- Diferentes representaciones del mismo objeto
Cuándo NO usar
- Objetos simples con pocos parámetros
- Todos los parámetros son obligatorios
- No necesitas inmutabilidad
- La complejidad adicional no se justifica
Ejemplo de uso
// Construcción fluent - solo configuras lo que necesitas
const dbConfig = new DatabaseConfigBuilder()
.host("localhost")
.port(5432)
.database("myapp")
.credentials("admin", "secret123")
.maxConnections(20)
.timeout(30)
.enableSSL()
.retryAttempts(3)
.build();
const connection = dbConfig.connect();
// vs Constructor tradicional (pesadilla de parámetros)
const dbConfig2 = new DatabaseConfig(
"localhost", // host
5432, // port
"myapp", // database
"admin", // username
"secret123", // password
20, // maxConnections
30, // timeout
true, // ssl
3, // retryAttempts
10, // poolSize
null, // ¿Qué es esto?
false, // ¿y esto?
null // ¿y esto otro?
);
Diferencias con otros patrones
Patrón | Propósito | Cuándo usar |
---|---|---|
Builder | Construir objetos complejos paso a paso | Muchos parámetros opcionales |
Factory Method | Crear objetos sin especificar clase exacta | Múltiples implementaciones |
Abstract Factory | Crear familias de objetos relacionados | Productos que deben ser compatibles |
Prototype | Crear objetos clonando existentes | Creación costosa, muchas variaciones |