Patrones de diseño explicados: guía breve con ejemplos
Un patrón de diseño es una solución reutilizable a un problema que aparece una y otra vez al construir software. No es una librería ni un framework: es un nombre acordado para una idea que ya funciona en muchos proyectos. Reconocer el patrón correcto simplifica decisiones de arquitectura y facilita que un equipo hable el mismo idioma.
Esta guía recorre quince patrones frecuentes. Cada uno incluye qué resuelve, un ejemplo mínimo en Python y una frase para recordarlo.
Patrones creacionales
Los creacionales deciden cómo se crean los objetos.
Singleton
Limita una clase a una sola instancia compartida. Sirve cuando todos los consumidores deben usar la misma configuración o el mismo recurso.
class Config:
_instancia = None
def __new__(cls):
if cls._instancia is None:
# crea el objeto una sola vez
cls._instancia = super().__new__(cls)
# reutiliza el mismo objeto
return cls._instancia
a = Config()
b = Config()
print(a is b) # True: a y b apuntan al mismo objeto
Recuerda: una sola instancia, muchos accesos.
Factory Method
Define un método de creación y deja que una subclase decida qué producto concreto devuelve. Evita fijar el tipo de objeto en el código cliente.
class Creador:
def crear(self):
raise NotImplementedError # cada creador concreto lo define
class CreadorPDF(Creador):
def crear(self):
return PDF() # PDF es el producto elegido
archivo = CreadorPDF().crear()
Recuerda: la subclase decide qué objeto crear.
Abstract Factory
Crea una familia de objetos relacionados que deben ser compatibles entre sí, sin depender de sus clases concretas.
class FabricaOscura:
def crear_boton(self):
return BotonOscuro()
def crear_campo(self):
return CampoOscuro()
fabrica = FabricaOscura()
boton = fabrica.crear_boton() # botón de la familia oscura
campo = fabrica.crear_campo() # campo de esa misma familia
Recuerda: una fábrica, varios objetos compatibles.
Prototype
Crea un objeto copiando uno existente. Conviene cuando configurar uno nuevo desde cero sería costoso.
from copy import copy
original = {"color": "azul"}
copia = copy(original) # crea una copia superficial
copia["color"] = "rojo"
print(original["color"]) # azul: el original no cambió
Recuerda: usar un objeto como molde. copy no copia profundamente los objetos anidados.
Simple Factory
Concentra en una función la lógica para elegir y crear un objeto. A diferencia de Factory Method, no necesita subclases creadoras.
def crear_documento(tipo):
if tipo == "pdf":
return PDF()
if tipo == "csv":
return CSV()
raise ValueError("Tipo no admitido") # evita una elección silenciosa
documento = crear_documento("pdf") # la función decide qué clase crear
Recuerda: una función centraliza la elección del objeto.
Patrones estructurales
Los estructurales conectan objetos o simplifican su uso.
Facade
Ofrece una operación sencilla que coordina varias partes de un sistema complejo. El cliente no necesita conocer todos los pasos internos.
class Cine:
def ver_pelicula(self):
luces.apagar() # paso interno 1
proyector.encender() # paso interno 2
sonido.encender() # paso interno 3
cine = Cine()
cine.ver_pelicula() # una llamada coordina el proceso
Recuerda: una entrada sencilla para varias operaciones. luces, proyector y sonido representan componentes ya configurados.
Decorator
Añade una responsabilidad a una función u objeto envolviéndolo, sin modificar su código original.
def en_mayusculas(funcion):
def envoltura(texto):
return funcion(texto).upper() # agrega comportamiento
return envoltura
@en_mayusculas
def saludar(nombre):
return f"Hola, {nombre}"
print(saludar("Ana")) # HOLA, ANA
Recuerda: envolver para añadir una capacidad.
Proxy
Coloca un intermediario delante del objeto real para controlar el acceso, validar permisos, registrar llamadas o retrasar la creación.
class Proxy:
def __init__(self, real):
self.real = real # guarda el objeto verdadero
def ejecutar(self):
print("Validando acceso") # control previo
self.real.ejecutar() # delega la operación
proxy = Proxy(ServicioReal())
proxy.ejecutar()
Recuerda: el proxy controla el acceso y delega el trabajo.
Patrones conductuales
Los conductuales organizan cómo colaboran y se comunican los objetos.
Command
Convierte una solicitud en un objeto con una operación ejecutable. Así se puede guardar, poner en cola o registrar una acción.
class EncenderLuz:
def __init__(self, luz):
self.luz = luz # guarda a quién afecta
def ejecutar(self):
self.luz.encender() # realiza la solicitud
comando = EncenderLuz(luz)
comando.ejecutar() # ejecuta la acción encapsulada
Recuerda: una acción empaquetada como objeto.
Memento
Guarda el estado de un objeto para recuperarlo después, sin que el código externo manipule sus detalles internos.
class Editor:
def __init__(self):
self.texto = ""
def guardar(self):
return self.texto # crea el recuerdo
def restaurar(self, estado):
self.texto = estado
editor = Editor()
editor.texto = "Versión 1"
recuerdo = editor.guardar()
editor.texto = "Versión 2"
editor.restaurar(recuerdo) # vuelve a Versión 1
Recuerda: guardar y restaurar un estado.
Observer
Permite que un objeto avise automáticamente a varios suscriptores cuando ocurre un cambio. Reduce la dependencia entre quien publica y quien recibe.
class Canal:
def __init__(self):
self.suscriptores = []
def suscribir(self, funcion):
self.suscriptores.append(funcion) # registra un observador
def publicar(self, mensaje):
for funcion in self.suscriptores:
funcion(mensaje) # avisa a cada suscriptor
canal = Canal()
canal.suscribir(print)
canal.publicar("Hay novedades")
Recuerda: un cambio, muchas notificaciones.
Strategy
Encapsula algoritmos intercambiables que cumplen el mismo propósito. El código cliente cambia la estrategia sin llenarse de condiciones.
def pagar_tarjeta(monto):
return f"Tarjeta: {monto}"
def pagar_efectivo(monto):
return f"Efectivo: {monto}"
elegir_pago = pagar_tarjeta # selecciona un algoritmo
print(elegir_pago(100))
elegir_pago = pagar_efectivo # cambia la estrategia
print(elegir_pago(100))
Recuerda: mismo objetivo, algoritmo intercambiable.
Acceso a datos y arquitectura
Tres piezas que no son patrones GoF, pero aparecen en casi cualquier aplicación real.
DAO
Separa las consultas y operaciones de persistencia de la lógica de negocio. Si cambia la fuente de datos, el resto de la aplicación depende menos de ese cambio.
class UsuarioDAO:
def __init__(self, db):
self.db = db
def buscar(self, id_usuario):
return self.db.obtener_usuario(id_usuario) # acceso aislado
usuario = UsuarioDAO(db).buscar(7)
Recuerda: el DAO sabe cómo acceder a los datos; la lógica de negocio le pide información.
Inyección de dependencias
Una clase recibe desde fuera los objetos que necesita, en lugar de crearlos por dentro. Facilita cambiar implementaciones y probar con objetos falsos.
class Servicio:
def __init__(self, repositorio):
self.repositorio = repositorio # la dependencia llega desde fuera
servicio = Servicio(repositorio_real)
# En una prueba puede usarse Servicio(repositorio_falso).
Recuerda: recibir dependencias, no construirlas internamente.
MVC
Divide la aplicación en Modelo (datos y reglas), Vista (presentación) y Controlador (interpreta acciones y coordina).
class Modelo:
def mensaje(self):
return "Hola"
class Vista:
def mostrar(self, texto):
print(texto)
class Controlador:
def __init__(self, modelo, vista):
self.modelo, self.vista = modelo, vista
def ejecutar(self):
self.vista.mostrar(self.modelo.mensaje()) # coordina ambos
Controlador(Modelo(), Vista()).ejecutar()
Recuerda: Modelo son datos y reglas; Vista es lo que se muestra; Controlador es la coordinación.
Resumen
- Creacionales: deciden cómo crear objetos.
- Estructurales: conectan objetos o simplifican su uso.
- Conductuales: organizan cómo colaboran y se comunican.
- DAO: aísla el acceso a datos.
- DI: recibe dependencias desde fuera.
- MVC: separa datos, interfaz y coordinación.
Los nombres PDF, CSV, BotonOscuro, CampoOscuro, ServicioReal, luces, proyector, sonido, luz, db y repositorio_real de los ejemplos representan clases u objetos definidos en la aplicación. Se muestran así para concentrar la atención en la idea del patrón.
© Arturo Juárez Monroy