Patrón Builder en Python
El patrón Builder nos permite crear diferentes representaciones de objetos utilizando el mismo proceso de construcción. Imagina el proceso de construir una casa: ya sea una villa o un apartamento, ambos requieren pasar por pasos como los cimientos, levantar muros, instalar fontanería y electricidad, pero el resultado final es completamente diferente.
Idea central
El patrón Builder separa el proceso de construcción de un objeto complejo de su representación, de modo que el mismo proceso de construcción pueda crear diferentes representaciones. Este patrón es especialmente adecuado para crear objetos complejos que constan de múltiples componentes.
¿Por qué se necesita el patrón Builder?
Problemas del método de construcción tradicional
Veamos primero un ejemplo sin usar el patrón Builder:
Ejemplo
def __init__(self, cpu, memory, storage, graphics_card, monitor):
self.cpu = cpu
self.memory = memory
self.storage = storage
self.graphics_card = graphics_card
self.monitor = monitor
def __str__(self):
return f"Computer: CPU={self.cpu}, Memory={self.memory}GB, Storage={self.storage}GB, Graphics={self.graphics_card}, Monitor={self.monitor}"
# Crear objeto de computadora
computer = Computer("Intel i7", 16, 512, "NVIDIA RTX 3060", "27 pulgadas 4K")
print(computer)
Desventajas de este método:
- Demasiados parámetros en el constructor, difíciles de mantener
- Hay que recordar el orden de todos los parámetros
- El proceso de creación no es lo suficientemente flexible
- Mala legibilidad del código
Componentes del patrón Builder
El patrón Builder incluye cuatro roles principales:

1. Product (Producto)
El objeto complejo que se va a crear
2. Builder (Constructor abstracto)
Define la interfaz abstracta para crear las diferentes partes del producto
3. ConcreteBuilder (Constructor concreto)
Implementa la interfaz Builder, construye y ensambla los componentes
4. Director (Director)
Construye un objeto que utiliza la interfaz Builder.
Implementación completa del código
Implementemos completamente el patrón Builder mediante un ejemplo de configuración de una computadora:
1. Clase de producto (Product)
Ejemplo
"""Clase de producto de computadora"""
def __init__(self):
self.cpu = None
self.memory = None
self.storage = None
self.graphics_card = None
self.monitor = None
def __str__(self):
specs = []
if self.cpu:
specs.append(f"CPU: {self.cpu}")
if self.memory:
specs.append(f"Memoria: {self.memory}GB")
if self.storage:
specs.append(f"Almacenamiento: {self.storage}GB")
if self.graphics_card:
specs.append(f"Tarjeta gráfica: {self.graphics_card}")
if self.monitor:
specs.append(f"Monitor: {self.monitor}")
return "Configuración de la computadora:\n" + "\n".join(f" - {spec}" for spec in specs)
2. Constructor abstracto (Builder)
Ejemplo
class ComputerBuilder(ABC):
"""Clase abstracta de constructor de computadoras"""
def __init__(self):
self.computer = Computer()
@abstractmethod
def build_cpu(self):
pass
@abstractmethod
def build_memory(self):
pass
@abstractmethod
def build_storage(self):
pass
@abstractmethod
def build_graphics_card(self):
pass
@abstractmethod
def build_monitor(self):
pass
def get_computer(self):
return self.computer
3. Constructor concreto (ConcreteBuilder)
Ejemplo
"""Constructor de computadoras para juegos"""
def build_cpu(self):
self.computer.cpu = "Intel i9-13900K"
def build_memory(self):
self.computer.memory = 32
def build_storage(self):
self.computer.storage = 2000 # 2TB
def build_graphics_card(self):
self.computer.graphics_card = "NVIDIA RTX 4090"
def build_monitor(self):
self.computer.monitor = "32 pulgadas 4K 144Hz"
class OfficeComputerBuilder(ComputerBuilder):
"""Constructor de computadoras de oficina"""
def build_cpu(self):
self.computer.cpu = "Intel i5-13400"
def build_memory(self):
self.computer.memory = 16
def build_storage(self):
self.computer.storage = 512
def build_graphics_card(self):
self.computer.graphics_card = "Tarjeta gráfica integrada"
def build_monitor(self):
self.computer.monitor = "24 pulgadas 1080P"
4. Director
Ejemplo
"""Director de construcción de computadoras"""
def __init__(self, builder):
self.builder = builder
def construct_computer(self):
"""Proceso completo de construcción de una computadora"""
self.builder.build_cpu()
self.builder.build_memory()
self.builder.build_storage()
self.builder.build_graphics_card()
self.builder.build_monitor()
def get_computer(self):
return self.builder.get_computer()
Ejemplo de uso
Ahora veamos cómo usar el patrón Builder para crear diferentes tipos de computadoras:
Ejemplo
print("=== Demostración del patrón Builder ==="\n")
# Crear computadora para juegos
print("1. Construcción de computadora para juegos:")
gaming_builder = GamingComputerBuilder()
director = ComputerDirector(gaming_builder)
director.construct_computer()
gaming_computer = director.get_computer()
print(gaming_computer)
print("\n" + "="*50 + "\n")
# Crear computadora de oficina
print("2. Construcción de computadora de oficina:")
office_builder = OfficeComputerBuilder()
director = ComputerDirector(office_builder)
director.construct_computer()
office_computer = director.get_computer()
print(office_computer)
print("\n" + "="*50 + "\n")
# Crear computadora personalizada
print("3. Construcción de computadora personalizada:")
custom_builder = CustomComputerBuilder()
custom_builder.build_cpu("AMD Ryzen 7 7800X3D")
custom_builder.build_memory(64)
custom_builder.build_storage(4000)
custom_builder.build_graphics_card("AMD RX 7900 XTX")
custom_builder.build_monitor("34 pulgadas ultraancho curvo")
custom_computer = custom_builder.get_computer()
print(custom_computer)
# Constructor personalizado (construcción de pasos opcionales)
class CustomComputerBuilder:
"""Constructor de computadoras personalizado"""
def __init__(self):
self.computer = Computer()
def build_cpu(self, cpu):
self.computer.cpu = cpu
return self # Devuelve a sí mismo, admite llamadas encadenadas
def build_memory(self, memory):
self.computer.memory = memory
return self
def build_storage(self, storage):
self.computer.storage = storage
return self
def build_graphics_card(self, graphics_card):
self.computer.graphics_card = graphics_card
return self
def build_monitor(self, monitor):
self.computer.monitor = monitor
return self
def get_computer(self):
return self.computer
if __name__ == "__main__":
main()
Variantes del patrón Builder
1. Builder con interfaz fluida
Ejemplo
def __init__(self):
self.size = None
self.cheese = False
self.pepperoni = False
self.mushrooms = False
def __str__(self):
ingredients = []
if self.cheese: ingredients.append("queso")
if self.pepperoni: ingredients.append("salchicha italiana")
if self.mushrooms: ingredients.append("champiñón")
return f"{self.size} pulgadas de pizza, ingredientes: {', '.join(ingredients)}"
class PizzaBuilder:
def __init__(self, size):
self.pizza = Pizza()
self.pizza.size = size
def add_cheese(self):
self.pizza.cheese = True
return self
def add_pepperoni(self):
self.pizza.pepperoni = True
return self
def add_mushrooms(self):
self.pizza.mushrooms = True
return self
def build(self):
return self.pizza
# Usar interfaz fluida
pizza = (PizzaBuilder(12)
.add_cheese()
.add_pepperoni()
.add_mushrooms()
.build())
print(pizza)
Ventajas y desventajas del patrón Builder
Ventajas
| Ventajas | Descripción |
|---|---|
| Buen encapsulamiento | El proceso de construcción está separado de la representación; el cliente no necesita conocer los detalles internos |
| Alta extensibilidad | Se pueden añadir fácilmente nuevos constructores concretos |
| Mejor control | Permite un control más preciso del proceso de construcción |
| Legibilidad del código | El código es más claro cuando se utiliza el encadenamiento de llamadas |
Desventajas
| Desventajas | Descripción |
|---|---|
| Aumenta la complejidad | Requiere definir múltiples clases, lo que aumenta la complejidad del sistema |
| No es adecuado cuando los productos difieren mucho | Si las diferencias entre productos son muy grandes, el patrón Builder no es muy adecuado |
| El constructor depende del producto | El constructor necesita conocer los detalles específicos del producto |
Casos de uso
Situaciones adecuadas para usar el patrón Builder:
- Crear objetos complejos: se necesita crear objetos complejos con múltiples componentes
- Proceso de construcción estable: el proceso de construcción del objeto es relativamente estable, pero los componentes del objeto cambian con frecuencia
- Múltiples representaciones: los objetos que se necesitan crear tienen varias representaciones diferentes
- Evitar constructores telescópicos: evitar usar constructores con muchos parámetros
Ejemplos de aplicación práctica:
- Conversor de documentos: convertir documentos a diferentes formatos como PDF, Word, HTML, etc.
- Preparación de comidas: preparar comidas de diferentes menús (combinaciones de hamburguesas, papas fritas, bebidas)
- Construcción de componentes de UI: construir componentes de interfaz de usuario complejos
- Generación de informes: generar informes que incluyen diferentes secciones (encabezados, datos, gráficos)
Ejercicios prácticos
Ejercicio 1: Implementar un constructor de automóviles
Intenta implementar un patrón Builder para un automóvil, con los siguientes componentes:
- Motor (gasolina, eléctrico, híbrido)
- Color de la carrocería (rojo, azul, negro, blanco)
- Tamaño de las ruedas (16 pulgadas, 18 pulgadas, 20 pulgadas)
- Interior (cuero, tela)
Ejercicio 2: Mejorar el constructor de computadoras
Agrega las siguientes funcionalidades al constructor de computadoras:
- Componentes opcionales (por ejemplo, si se instala una unidad de disco)
- Lógica de validación (por ejemplo, la memoria no puede ser inferior a 4 GB)
- Función de cálculo de precio
Resumen
El patrón Builder es un potente patrón de diseño creacional que descompone el proceso de construcción de objetos complejos en varios pasos simples, haciendo que el proceso de creación sea más flexible y controlable. Aunque aumenta la complejidad del sistema, en escenarios donde se necesitan crear objetos complejos y el proceso de construcción es relativamente fijo, el patrón Builder puede mejorar significativamente la mantenibilidad y extensibilidad del código.
Recuerda la idea central de los patrones de diseño:No usar el patrón solo por usarlo, sino para resolver problemas de diseño específicos.. En el desarrollo real, debes decidir si usar el patrón Builder según los requisitos concretos.
Otras extensiones