Patrones de diseño explicados: guía breve con ejemplos

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