Patrón de Diseño Builder: El Maestro Constructor Personalizado 🍔
Imagina que estás en un McDonald’s y quieres pedir una hamburguesa. Pero no cualquier hamburguesa, sino TU hamburguesa perfecta: pan integral, doble carne, sin pepinillos, extra queso, salsa barbacoa, lechuga iceberg y tomate fresco.

El Patrón de Diseño Builder es como ese empleado experto que va construyendo tu hamburguesa paso a paso, ingrediente por ingrediente, hasta crear exactamente lo que pediste. Su función es permitirte construir objetos complejos de forma gradual y controlada, donde puedes elegir qué partes incluir y cuáles no.
Un Poco de Historia 📖
El patrón de diseño Builder fue catalogado por la Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides) en su libro “Design Patterns: Elements of Reusable Object-Oriented Software” de 1994. Se clasifica como un patrón creacional, es decir, se ocupa de cómo se crean los objetos.
El objetivo original era separar la construcción de un objeto complejo de su representación, de modo que el mismo proceso de construcción pudiera crear diferentes representaciones. Piensa en un conversor de documentos: el mismo proceso de lectura puede generar un PDF, un HTML o un texto plano según el builder que uses.
Con los años, el patrón ha evolucionado. La versión moderna más popular es el Fluent Builder (con method chaining), que es la que veremos aquí y la que probablemente uses en tu día a día sin saberlo.
Constructores Telescópicos 🔭
Antes de ver el Builder en acción, entendamos por qué existe. Imagina que tienes una clase con muchos parámetros opcionales:
// 💀 El anti-patrón "Telescoping Constructor"
$hamburguesa = new Hamburguesa(
'sésamo', // pan
'vacuno', // carne
2, // cantidad de carne
true, // lechuga
true, // tomate
false, // pepinillos
true, // cebolla
'cheddar', // queso
null, // salsa 1
'ketchup', // salsa 2
null, // salsa 3
true, // bacon
false // huevo
);
// ¿Qué collons es cada parámetro? 🤯
Esto es lo que se conoce como Constructor Telescópico: una función con tantos parámetros que es imposible recordar
qué va en cada posición. ¿El true del quinto parámetro es para pepinillos o para tomate? Ni idea. Y si quieres añadir
un ingrediente nuevo, tienes que modificar todos los constructores existentes.
Constructores con 10+ parámetros, imposibles de leer. Cada null o true es un misterio. Añadir un campo nuevo rompe todo.
Construcción paso a paso, cada método dice lo que hace. ->agregarQueso('cheddar') se explica solo. Extensible sin romper nada.
¿Qué problema principal resuelve el patrón Builder?
¿En qué Consiste?
Este patrón te permite crear objetos complejos paso a paso, especificando solo las partes que necesitas sin tener constructores gigantes con mil parámetros. La clave está en separar la construcción del objeto de su representación final.
Los Cuatro Roles Principales
El objeto complejo que estás construyendo. Es el resultado final de todo el proceso.
Ejemplo: La Hamburguesa terminada
La interfaz que define qué pasos de construcción están disponibles. Es el contrato que todos los constructores deben cumplir.
Ejemplo: HamburguesaBuilder (interfaz)
Implementaciones específicas del Builder. Cada una sabe construir una variante del producto.
Ejemplo: BigMacBuilder, VeggieBuilder
Opcional pero útil. Conoce las recetas (secuencias de pasos) para crear productos populares.
Ejemplo: ChefHamburguesas (recetas clásicas)
Construcción Flexible y Legible
Lo guapo del Builder es que puedes construir el mismo tipo de objeto de mil maneras diferentes. ¿Quieres una hamburguesa solo con pan y carne? Pos vale. ¿Quieres una con todos los ingredientes del universo? Pos venga. Y lo más importante: tu código queda súper legible y fácil de entender.
¿Cuáles son los cuatro roles principales del patrón Builder?
Ejemplo de la Vida Real: McDonald’s vs. Burger King 🍟
Imagina que programas el sistema de pedidos de una cadena de hamburguesas:
| Componente del Patrón | Ejemplo en Hamburguesas |
|---|---|
| Product | Hamburguesa (El producto final) |
| Builder | HamburguesaBuilder (Métodos: agregarPan(), agregarCarne(), agregarQueso()) |
| ConcreteBuilder | BigMacBuilder, WhopperBuilder, VeggieBuilder |
| Director | ChefHamburguesas (Conoce las recetas clásicas) |
Flujo de Trabajo
- Elección del Constructor: El cliente decide qué tipo de hamburguesa quiere:
new BigMacBuilder(). - Construcción Paso a Paso: Tu código va añadiendo ingredientes:
$builder->agregarPan('sésamo')$builder->agregarCarne('vacuno', 2)$builder->agregarQueso('cheddar')
- Obtención del Resultado: Al final:
$hamburguesa = $builder->build() - Flexibilidad Total: Si mañana quieres una Whopper, solo cambias el builder (
new WhopperBuilder()), pero el proceso de construcción es igual.
Ejemplo de Código en PHP
Este es un ejemplo simplificado de cómo se vería la estructura en PHP, siguiendo el ejemplo de las hamburguesas.
1. El Producto (Product)
<?php
class Hamburguesa {
private string $pan = '';
private array $carnes = [];
private array $verduras = [];
private array $salsas = [];
private array $extras = [];
public function setPan(string $pan): void {
$this->pan = $pan;
}
public function addCarne(string $tipo, int $cantidad = 1): void {
for ($i = 0; $i < $cantidad; $i++) {
$this->carnes[] = $tipo;
}
}
public function addVerdura(string $verdura): void {
$this->verduras[] = $verdura;
}
public function addSalsa(string $salsa): void {
$this->salsas[] = $salsa;
}
public function addExtra(string $extra): void {
$this->extras[] = $extra;
}
public function mostrar(): string {
$descripcion = "🍔 HAMBURGUESA PERSONALIZADA 🍔\n";
$descripcion .= "Pan: " . ($this->pan ?: 'Sin especificar') . "\n";
$descripcion .= "Carnes: " . (empty($this->carnes) ? 'Ninguna' : implode(', ', $this->carnes)) . "\n";
$descripcion .= "Verduras: " . (empty($this->verduras) ? 'Ninguna' : implode(', ', $this->verduras)) . "\n";
$descripcion .= "Salsas: " . (empty($this->salsas) ? 'Ninguna' : implode(', ', $this->salsas)) . "\n";
$descripcion .= "Extras: " . (empty($this->extras) ? 'Ninguno' : implode(', ', $this->extras)) . "\n";
return $descripcion;
}
}
2. La Interfaz Builder (Abstract Builder)
<?php
interface HamburguesaBuilder {
public function reset(): void;
public function agregarPan(string $tipo): self;
public function agregarCarne(string $tipo, int $cantidad = 1): self;
public function agregarVerdura(string $verdura): self;
public function agregarSalsa(string $salsa): self;
public function agregarExtra(string $extra): self;
public function build(): Hamburguesa;
}
3. Los Constructores Concretos (Concrete Builders)
<?php
class BigMacBuilder implements HamburguesaBuilder {
private Hamburguesa $hamburguesa;
public function __construct() {
$this->reset();
}
public function reset(): void {
$this->hamburguesa = new Hamburguesa();
}
public function agregarPan(string $tipo): self {
$this->hamburguesa->setPan($tipo);
return $this;
}
public function agregarCarne(string $tipo, int $cantidad = 1): self {
$this->hamburguesa->addCarne($tipo, $cantidad);
return $this;
}
public function agregarVerdura(string $verdura): self {
$this->hamburguesa->addVerdura($verdura);
return $this;
}
public function agregarSalsa(string $salsa): self {
$this->hamburguesa->addSalsa($salsa);
return $this;
}
public function agregarExtra(string $extra): self {
$this->hamburguesa->addExtra($extra);
return $this;
}
public function build(): Hamburguesa {
$resultado = $this->hamburguesa;
$this->reset(); // Preparamos para la siguiente hamburguesa
return $resultado;
}
}
class VeggieBuilder implements HamburguesaBuilder {
private Hamburguesa $hamburguesa;
public function __construct() {
$this->reset();
}
public function reset(): void {
$this->hamburguesa = new Hamburguesa();
}
public function agregarPan(string $tipo): self {
$this->hamburguesa->setPan($tipo);
return $this;
}
public function agregarCarne(string $tipo, int $cantidad = 1): self {
// En la versión veggie, "carne" sería proteína vegetal
$this->hamburguesa->addCarne("Proteína vegetal ({$tipo})", $cantidad);
return $this;
}
public function agregarVerdura(string $verdura): self {
$this->hamburguesa->addVerdura($verdura);
return $this;
}
public function agregarSalsa(string $salsa): self {
$this->hamburguesa->addSalsa($salsa);
return $this;
}
public function agregarExtra(string $extra): self {
$this->hamburguesa->addExtra($extra);
return $this;
}
public function build(): Hamburguesa {
$resultado = $this->hamburguesa;
$this->reset();
return $resultado;
}
}
¿Por qué el método build() del Builder suele llamar a reset() internamente?
4. El Director (Opcional pero Útil)
<?php
class ChefHamburguesas {
public function hacerBigMacClasica(HamburguesaBuilder $builder): Hamburguesa {
return $builder
->agregarPan('sésamo')
->agregarCarne('vacuno', 2)
->agregarVerdura('lechuga')
->agregarVerdura('cebolla')
->agregarVerdura('pepinillos')
->agregarSalsa('Big Mac sauce')
->agregarExtra('queso cheddar')
->build();
}
public function hacerVeggieSuprema(HamburguesaBuilder $builder): Hamburguesa {
return $builder
->agregarPan('integral')
->agregarCarne('soja')
->agregarVerdura('lechuga')
->agregarVerdura('tomate')
->agregarVerdura('aguacate')
->agregarVerdura('cebolla morada')
->agregarSalsa('mostaza')
->agregarSalsa('mayonesa vegana')
->agregarExtra('queso vegano')
->build();
}
}
¿Qué rol del patrón Builder es opcional?
5. El Código Cliente (El Que Hace los Pedidos)
<?php
// Forma 1: Construcción manual (máxima flexibilidad)
echo "=== PEDIDO PERSONALIZADO ===\n";
$builder = new BigMacBuilder();
$miHamburguesa = $builder
->agregarPan('brioche')
->agregarCarne('vacuno', 1)
->agregarVerdura('lechuga')
->agregarVerdura('tomate')
->agregarSalsa('ketchup')
->agregarExtra('bacon')
->agregarExtra('queso gouda')
->build();
echo $miHamburguesa->mostrar();
// Forma 2: Usando el Director (recetas pre-definidas)
echo "\n=== PEDIDOS CON RECETAS CLÁSICAS ===\n";
$chef = new ChefHamburguesas();
// Big Mac clásica
$builderBigMac = new BigMacBuilder();
$bigMac = $chef->hacerBigMacClasica($builderBigMac);
echo $bigMac->mostrar();
// Veggie suprema
$builderVeggie = new VeggieBuilder();
$veggieSuprema = $chef->hacerVeggieSuprema($builderVeggie);
echo $veggieSuprema->mostrar();
¿Qué técnica permite encadenar llamadas como $builder->agregarPan('integral')->agregarCarne('pollo')->build()?
Otro Ejemplo Real: QueryBuilder (SQL) 🗄️
Las hamburguesas están muy bien para entender el concepto, pero vamos a ver un ejemplo que probablemente ya usas sin saber que es un Builder. Si has trabajado con Laravel, Doctrine o cualquier ORM moderno, ya conoces el QueryBuilder:
<?php
class QueryBuilder {
private string $table = '';
private array $conditions = [];
private array $columns = ['*'];
private ?int $limit = null;
private ?string $orderBy = null;
private string $orderDirection = 'ASC';
public function table(string $table): self {
$this->table = $table;
return $this;
}
public function select(string ...$columns): self {
$this->columns = $columns;
return $this;
}
public function where(string $column, string $operator, mixed $value): self {
$this->conditions[] = "{$column} {$operator} '{$value}'";
return $this;
}
public function orderBy(string $column, string $direction = 'ASC'): self {
$this->orderBy = $column;
$this->orderDirection = $direction;
return $this;
}
public function limit(int $limit): self {
$this->limit = $limit;
return $this;
}
public function build(): string {
$query = "SELECT " . implode(', ', $this->columns);
$query .= " FROM {$this->table}";
if (!empty($this->conditions)) {
$query .= " WHERE " . implode(' AND ', $this->conditions);
}
if ($this->orderBy) {
$query .= " ORDER BY {$this->orderBy} {$this->orderDirection}";
}
if ($this->limit !== null) {
$query .= " LIMIT {$this->limit}";
}
return $query;
}
}
// Uso: ¡Se lee como una frase!
$query = (new QueryBuilder())
->table('usuarios')
->select('nombre', 'email', 'fecha_registro')
->where('activo', '=', '1')
->where('rol', '=', 'admin')
->orderBy('fecha_registro', 'DESC')
->limit(10)
->build();
// SELECT nombre, email, fecha_registro
// FROM usuarios
// WHERE activo = '1' AND rol = 'admin'
// ORDER BY fecha_registro DESC
// LIMIT 10
Cada método describe exactamente lo que hace. No hay parámetros misteriosos ni booleans que nadie recuerda. Es como leer la query en lenguaje natural.
En el ejemplo del QueryBuilder, ¿qué principio SOLID se aplica al poder añadir nuevos métodos (como groupBy()) sin modificar los existentes?
Builder en el Mundo Real: Donde Ya Lo Usas 🌍
El Builder está por todas partes, aunque no lo notes. Aquí tienes ejemplos reales de frameworks y librerías que lo usan:
DB::table('users')->where(...)->get() — El QueryBuilder de Eloquent es un Builder puro.
FormBuilder para construir formularios complejos paso a paso con validaciones.
MockBuilder para crear mocks configurables: ->setMethods()->getMock()
new URL() y librerías como Knex.js usan Builders para construir queries.
StringBuilder, Stream.builder(), y Lombok con @Builder.
El propio Dockerfile es un Builder: cada instrucción (FROM, RUN, COPY) construye la imagen paso a paso.
Builder vs. Otros Patrones Creacionales 🆚
Es fácil confundir el Builder con otros patrones que también crean objetos, aquí va la comparativa:
| Característica | Builder | Factory Method | Abstract Factory | Prototype |
|---|---|---|---|---|
| Propósito | Construir objetos complejos paso a paso | Delegar la creación a subclases | Crear familias de objetos relacionados | Clonar objetos existentes |
| Complejidad del objeto | Alta (muchas partes opcionales) | Media | Media-Alta | Variable |
| ¿Cuándo usarlo? | Muchos parámetros opcionales | Una familia de productos con variantes | Múltiples familias de productos | Cuando crear desde cero es costoso |
| Flexibilidad | Máxima (configuras cada paso) | Media (eliges la variante) | Media (eliges la familia) | Alta (modificas el clon) |
| Ejemplo rápido | Construir una pizza ingrediente a ingrediente | Crear notificaciones (email, SMS, push) | Crear UI para Windows/macOS/Linux | Copiar un documento y modificarlo |
Si tu objeto necesita muchos pasos opcionales para construirse, usa Builder. Si simplemente necesitas elegir qué tipo crear, usa Factory Method.
¿Cuál es la diferencia principal entre Builder y Factory Method?
¿Cuándo Usar el Patrón Builder? 🤔
El Builder es perfecto cuando:
- Objetos complejos: Tienes que crear objetos con muchas propiedades opcionales
- Constructores gigantes: Te cansas de ver constructores con 10+ parámetros (constructor telescópico)
- Flexibilidad: Quieres poder crear el mismo objeto de diferentes maneras
- Legibilidad: Prefieres código que se lea como una receta paso a paso
- Inmutabilidad: Quieres que el objeto final sea inmutable una vez construido
¿Cuándo NO usarlo? ⚠️
- Objetos simples: Si tu objeto tiene 2-3 propiedades, es overkill. Un constructor normal va sobrado.
- Todas las propiedades son obligatorias: Si siempre necesitas todos los campos, un constructor clásico es más directo.
- No hay variantes: Si solo hay una forma de construir el objeto, el Builder solo añade complejidad innecesaria.
¿Cuándo NO es recomendable usar el patrón Builder?
Builder y los Principios SOLID 🏛️
El Builder se lleva genial con SOLID:
El Product solo se preocupa de almacenar datos. El Builder solo de construir. El Director solo de conocer recetas. Cada clase tiene una sola razón para cambiar.
¿Quieres una nueva receta? Creas un nuevo ConcreteBuilder o un nuevo método en el Director. Sin tocar el código existente.
Cualquier ConcreteBuilder se puede usar donde se espere la interfaz Builder. El BigMacBuilder y el VeggieBuilder son intercambiables.
El Director depende de la interfaz HamburguesaBuilder, no de implementaciones concretas. Puedes pasarle cualquier builder.
Ventajas y Desventajas ⚖️
- Código ultra legible (method chaining)
- Construcción paso a paso controlada
- Reutilización de lógica de construcción
- Fácil de testear cada paso
- Objetos inmutables tras la construcción
- Elimina constructores telescópicos
- Más clases en el proyecto
- Overkill para objetos simples
- La interfaz puede crecer mucho
- Puede ocultar dependencias obligatorias
- Duplicación de código entre builders
Ejercicio Práctico 🧠
Para que termines de entender la potencia del Builder, te propongo este ejercicio:
Tu tarea: Añadir una nueva receta al ChefHamburguesas: la “Hamburguesa Fitness”.
- Debe usar pan integral
- Proteína: pechuga de pollo a la plancha
- Verduras: lechuga, tomate, pepino
- Sin salsas grasas (solo mostaza)
- Extra: aguacate
Crea el método hacerHamburguesaFitness() en la clase ChefHamburguesas y pruébalo con cualquiera de los builders.
Si consigues que imprima tu hamburguesa fitness sin tocar las clases Hamburguesa o los builders, dominas el patrón Builder. Eso es Open/Closed total.
Bonus: Crea un FitnessBuilder que automáticamente sustituya las salsas grasas por versiones light. Así practicas tanto el ConcreteBuilder como el Director.
EA, nos vemos en los bares! 🍺 saluditos