Cuando llevas un tiempo programando con orientación a objetos, empiezas a notar que ciertos problemas se repiten una y otra vez: necesitas que exista una sola instancia de algo, quieres crear objetos sin acoplarte a sus clases concretas, o necesitas que varios componentes reaccionen cuando algo cambia. Los patrones de diseño son precisamente eso: soluciones probadas, con nombre propio, a problemas recurrentes del diseño de software. En esta lección aprenderás qué es exactamente un patrón de diseño, qué elementos lo componen, qué cosas no son un patrón (una confusión muy habitual) y verás un primer problema real que nos servirá de motivación. Además, conocerás PideYa, el proyecto que nos acompañará durante todo el curso.
Contenido
- El proyecto del curso: PideYa
- Definición de patrón de diseño
- Los cuatro elementos esenciales: contexto, problema, solución y consecuencias
- Qué NO es un patrón de diseño
- Cómo se describe un patrón: la ficha de un patrón
- Un primer vistazo motivador: un problema real en PideYa
El proyecto del curso: PideYa
A lo largo de todo el curso trabajaremos sobre un mismo proyecto: PideYa, una plataforma ficticia de pedidos de comida a domicilio. La idea es que no aprendas los patrones con ejemplos abstractos de ClaseA y ClaseB, sino aplicándolos a un dominio realista que iremos haciendo crecer módulo a módulo.
PideYa tiene los ingredientes típicos de una aplicación de este tipo:
- Restaurantes con su carta de platos, precios y horarios.
- Clientes que navegan por las cartas, montan un carrito y realizan pedidos.
- Pagos a través de distintas pasarelas (tarjeta, PayPal, monedero interno...).
- Repartidores que recogen y entregan los pedidos.
- Notificaciones a clientes y repartidores (email, SMS, push) según avanza el pedido.
Este dominio es una mina de oro para los patrones de diseño: hay objetos complejos que construir (un pedido con sus líneas, descuentos y dirección de entrega), familias de objetos intercambiables (pasarelas de pago, canales de notificación), estados que cambian (un pedido pasa de recibido a en preparación, en reparto y entregado), y componentes que deben enterarse de lo que hacen otros sin acoplarse entre sí. Cada vez que un ejemplo lo permita, lo plantearemos sobre PideYa.
flowchart LR
C[Cliente] -->|monta carrito| P[Pedido]
P -->|paga con| PG[Pasarela de pago]
P -->|se asigna a| R[Repartidor]
P -->|genera| N[Notificaciones]
Rest[Restaurante] -->|prepara| P
No necesitas memorizar nada de PideYa ahora: iremos presentando cada pieza cuando la necesitemos.
Definición de patrón de diseño
Una definición práctica y ampliamente aceptada es la siguiente:
Un patrón de diseño es una solución general, reutilizable y con nombre propio a un problema de diseño que aparece de forma recurrente en un contexto determinado del desarrollo de software orientado a objetos.
Desglosemos esta definición, porque cada palabra importa:
- Solución general: el patrón no resuelve tu problema concreto, sino la familia de problemas a la que pertenece el tuyo. Por eso siempre hay que adaptarlo.
- Reutilizable: ha sido aplicado con éxito muchas veces, en muchos proyectos. Un truco que solo funcionó una vez no es un patrón; un patrón está destilado de la experiencia colectiva.
- Con nombre propio: esto es más importante de lo que parece. Decir "aquí usamos un Observer" comunica en dos palabras una estructura completa de clases, responsabilidades y colaboraciones. Los patrones crean un vocabulario común entre desarrolladores.
- Problema recurrente: si el problema solo aparece en tu proyecto, la solución puede ser buena, pero no es un patrón.
- En un contexto determinado: ningún patrón es bueno o malo en abstracto. Un patrón es adecuado en su contexto y contraproducente fuera de él.
Una analogía útil: un patrón de diseño es al software lo que una receta clásica es a la cocina. La receta de la tortilla de patatas te dice los ingredientes, los pasos y los resultados esperados, pero cada cocinero la adapta (¿con cebolla o sin cebolla?). Nadie "copia y pega" una tortilla: la cocina siguiendo el patrón.
Los cuatro elementos esenciales: contexto, problema, solución y consecuencias
Todo patrón de diseño, sea cual sea, se articula sobre cuatro elementos. Si falta alguno, no tienes un patrón completo, tienes solo un fragmento de idea.
| Elemento | Pregunta que responde | Ejemplo informal |
|---|---|---|
| Contexto | ¿En qué situación aparece el problema? | "Una aplicación con varios canales de notificación que pueden crecer en el futuro" |
| Problema | ¿Qué conflicto o fuerza en tensión hay que resolver? | "El código que envía notificaciones no debería cambiar cada vez que añadimos un canal nuevo" |
| Solución | ¿Qué estructura de clases/objetos y colaboraciones lo resuelve? | "Definir una abstracción común y hacer que el emisor dependa solo de ella" |
| Consecuencias | ¿Qué ganamos y qué pagamos al aplicarla? | "Ganamos extensibilidad; pagamos con más clases e indirección" |
Merece la pena detenerse en dos de ellos:
Las «fuerzas» del problema
El problema de un patrón nunca es trivial del tipo "quiero sumar dos números". Siempre hay fuerzas en tensión: requisitos que tiran en direcciones opuestas. Por ejemplo: quiero flexibilidad para cambiar el comportamiento en tiempo de ejecución (tira hacia más indirección) frente a quiero un código simple y directo (tira hacia menos clases). El patrón es un equilibrio documentado entre esas fuerzas.
Las consecuencias: no hay patrón gratis
Este punto separa al profesional del aficionado: todo patrón tiene un coste. Más clases, más indirección, más conceptos que el siguiente desarrollador debe entender. Un patrón bien aplicado es aquel cuyo beneficio supera claramente a su coste en ese contexto. Dedicaremos la lección Ventajas y Desventajas de Usar Patrones de Diseño a analizar esto en detalle.
Qué NO es un patrón de diseño
Aquí se concentran los malentendidos más frecuentes. Aclaremos los tres principales:
Un patrón NO es código copiable
No existe "el código del patrón Observer" que puedas pegar en tu proyecto. Un patrón describe una estructura y unas responsabilidades; el código concreto lo escribes tú cada vez, adaptado a tu dominio, tu lenguaje y tus restricciones. Dos implementaciones correctas del mismo patrón pueden no compartir ni una sola línea idéntica. Cuando en este curso veas código Java de un patrón, entiéndelo como una materialización posible, no como la implementación oficial.
Un patrón NO es un framework ni una librería
Un framework (Spring, Hibernate...) es software concreto que ejecutas; un patrón es conocimiento que aplicas. La relación real es la inversa: los frameworks están construidos con patrones. Spring, por ejemplo, usa internamente decenas de ellos, y por eso entender patrones te hace mucho mejor usuario de frameworks: dejas de ver "magia" y empiezas a ver estructuras conocidas.
| Patrón de diseño | Framework / librería | |
|---|---|---|
| Naturaleza | Conocimiento, descripción | Código ejecutable |
| Cómo se usa | Se adapta e implementa cada vez | Se importa y se invoca |
| Dependencia | Ninguna: vive en tu cabeza y en tu diseño | Tu proyecto depende de él |
| Nivel | Diseño (micro-arquitectura de clases) | Implementación |
Un patrón NO es un algoritmo
Un algoritmo (ordenación rápida, Dijkstra...) resuelve un problema computacional: dada una entrada, produce una salida en ciertos pasos, y su corrección se puede demostrar. Un patrón resuelve un problema de organización del código: cómo repartir responsabilidades entre clases para que el sistema sea mantenible y extensible. Un algoritmo se mide en eficiencia (tiempo, memoria); un patrón se mide en cualidades de diseño (acoplamiento, cohesión, extensibilidad).
Cómo se describe un patrón: la ficha de un patrón
Los patrones se documentan en catálogos, y cada patrón se presenta con una ficha estandarizada. La estructura clásica (heredada del catálogo del Gang of Four, cuya historia veremos en la siguiente lección) incluye estas secciones:
- Nombre: corto y evocador. Es la pieza de vocabulario que compartirás con tu equipo.
- Intención (intent): una o dos frases con lo que el patrón consigue.
- También conocido como: otros nombres con los que circula.
- Motivación: un escenario concreto donde el problema aparece y el patrón lo resuelve.
- Aplicabilidad: señales de cuándo usarlo (y, por omisión, cuándo no).
- Estructura: diagrama de clases con los participantes. En este curso los dibujaremos con mermaid, y en la lección UML Esencial para Entender Patrones aprenderás a leerlos.
- Participantes: cada clase/interfaz del diagrama y su responsabilidad.
- Colaboraciones: cómo interactúan los participantes en tiempo de ejecución.
- Consecuencias: beneficios y costes.
- Implementación: consejos, variantes y trampas al llevarlo a código.
- Código de ejemplo: una materialización en un lenguaje concreto.
- Usos conocidos: dónde se ha aplicado en sistemas reales.
- Patrones relacionados: alternativas y combinaciones habituales.
En este curso usaremos una versión aligerada de esta ficha para cada patrón de los módulos 2, 3 y 4: intención, problema en PideYa, estructura en mermaid, implementación en Java, consecuencias y patrones relacionados. Acostúmbrate a esta estructura: leer patrones "en ficha" es una habilidad en sí misma, porque los catálogos profesionales (libros, wikis de empresa) usan este formato.
Un primer vistazo motivador: un problema real en PideYa
Veamos por qué todo esto importa, con un problema real de PideYa. Atención: aquí solo vamos a sentir el dolor del problema; las soluciones con nombre y apellidos llegarán en sus lecciones correspondientes.
Cuando un pedido cambia de estado (por ejemplo, el restaurante lo marca como "en preparación"), hay que avisar a varias partes: al cliente por notificación push, al repartidor asignado, y al panel de estadísticas internas. Una primera versión ingenua podría ser así:
public class Pedido {
private String estado;
public void cambiarEstado(String nuevoEstado) {
this.estado = nuevoEstado;
// Avisar al cliente por push
ServicioPush push = new ServicioPush();
push.enviar(this.getCliente().getTokenDispositivo(),
"Tu pedido está: " + nuevoEstado);
// Avisar al repartidor por SMS
ServicioSms sms = new ServicioSms();
sms.enviar(this.getRepartidor().getTelefono(),
"Pedido " + this.getId() + ": " + nuevoEstado);
// Actualizar estadísticas
PanelEstadisticas panel = new PanelEstadisticas();
panel.registrarCambioEstado(this.getId(), nuevoEstado);
}
}Expliquemos qué hace este código y por qué, aunque funciona, es un problema esperando a crecer:
- El método
cambiarEstadoactualiza el estado y, a continuación, él mismo crea y llama a cada servicio interesado: push, SMS y estadísticas. - La clase
Pedido, que debería ocuparse de la lógica de negocio de un pedido, conoce los detalles de tres sistemas que no son asunto suyo (cómo se envía un push, qué teléfono tiene el repartidor, que existe un panel de estadísticas).
Ahora imagina la evolución típica del producto:
- Marketing pide que también se envíe un email en ciertos estados. → Hay que tocar
Pedido. - Se añade un programa de fidelización que suma puntos al entregar. → Hay que tocar
Pedido. - En los tests unitarios de
Pedido, cada test envía SMS reales (¡y cuestan dinero!) porque los servicios se crean connewdentro del método. → Difícil de testear. - El repartidor puede no estar asignado aún →
NullPointerExceptionen producción.
El problema de fondo, en términos de patrón, sería:
- Contexto: un objeto de negocio cuyos cambios interesan a un número variable de terceros.
- Problema: el objeto no debería conocer a sus interesados ni cambiar cada vez que aparece uno nuevo; las fuerzas en tensión son notificar a todos frente a no acoplarse a nadie.
- Solución: existe una estructura clásica que resuelve exactamente esto... y tiene nombre. La estudiaremos en el módulo 4 (Observer). También verás que la creación de esos servicios con
newtoca de lleno a los patrones creacionales del módulo 2. - Consecuencias: las analizaremos allí, porque tampoco esa solución es gratis.
Esto es lo que te dará el curso: la capacidad de reconocer estas situaciones, ponerles nombre y aplicar la solución destilada por miles de desarrolladores antes que tú, conociendo su precio.
Errores Comunes y Consejos
- Creer que aprender patrones es memorizar diagramas. Lo importante es reconocer el problema y sus fuerzas; la estructura se deduce (o se consulta) después. Si memorizas la solución sin el problema, aplicarás patrones donde no tocan.
- Buscar "el código del patrón" para copiarlo. No existe. Existen ejemplos, como los de este curso, que debes adaptar a tu dominio. Si copias sin adaptar, acabarás con nombres genéricos (
Manager,Handler) que no dicen nada de tu negocio. - Confundir patrón con framework. Si alguien dice "usamos el patrón Spring", hay una confusión de conceptos. Spring usa patrones; no es un patrón.
- Querer aplicar un patrón cuanto antes. El impulso de "meter patrones" en cuanto se aprenden es casi universal (y peligroso). Primero domina el problema que resuelve cada uno; la lección 01-06 y el módulo 5 te darán criterios para decidir.
- Consejo: cuando leas código ajeno (o un framework), practica poniendo nombre a las estructuras que reconozcas. Es el mejor entrenamiento posible y acelera muchísimo la lectura de código.
Ejercicios
Ejercicio 1: contexto, problema, solución y consecuencias
Piensa en la siguiente situación de PideYa y redacta los cuatro elementos de un posible patrón (sin nombrar ningún patrón concreto, aunque lo conozcas): "La aplicación móvil de PideYa debe funcionar con tres pasarelas de pago distintas (tarjeta, PayPal y monedero interno), y el equipo comercial ya está negociando con una cuarta."
Ejercicio 2: ¿patrón o no patrón?
Indica, para cada elemento, si puede considerarse un patrón de diseño y por qué sí o por qué no:
- El algoritmo de ordenación quicksort.
- La librería
java.util.logging. - "Cuando varias clases comparten un comportamiento que varía, extraerlo a una jerarquía propia e inyectarlo, en lugar de heredar."
- Un fichero
Utilidades.javacon métodos estáticos que tu equipo copia de proyecto en proyecto.
Ejercicio 3: detectar las fuerzas
Relee el código de Pedido.cambiarEstado(...) de esta lección. Enumera al menos tres "fuerzas" o requisitos en tensión que hacen que ese diseño sea problemático, expresándolos como pares del tipo "queremos X, pero también queremos Y".
Soluciones
Solución 1 (una redacción posible):
- Contexto: una aplicación de pedidos que cobra a sus clientes mediante proveedores de pago externos, cuyo número y variedad crece con el negocio.
- Problema: el código de cobro no debe reescribirse con cada pasarela nueva; cada pasarela tiene su propia API, pero el proceso de pedido debería tratarlas de forma uniforme. Fuerzas: uniformidad para el que cobra frente a diversidad real de las APIs.
- Solución (esbozo): definir una abstracción común de "pago" de la que dependa el proceso de pedido, y encapsular las particularidades de cada proveedor detrás de esa abstracción, de modo que añadir una pasarela sea añadir una pieza nueva, no modificar las existentes.
- Consecuencias: se gana extensibilidad y capacidad de probar el cobro sin pasarelas reales; se paga con una capa más de indirección y con el esfuerzo de diseñar una abstracción que encaje con APIs muy distintas.
Solución 2:
- No. Es un algoritmo: resuelve un problema computacional con entrada y salida definidas, y se evalúa por eficiencia, no por cualidades de diseño.
- No. Es una librería: código concreto que importas y ejecutas. (Internamente aplica patrones, pero ella misma no lo es.)
- Sí (o al menos tiene la forma de uno): describe un problema recurrente, una solución general en términos de estructura y responsabilidades, y es adaptable a muchos contextos. De hecho, apunta a un principio que veremos en 01-03: favorecer la composición sobre la herencia.
- No. Es código copiable, justo lo que un patrón no es. Puede ser útil, pero no describe problema, contexto ni consecuencias: solo es una implementación concreta que viaja entre proyectos.
Solución 3 (tres ejemplos válidos; hay más):
- Queremos que todos los interesados se enteren del cambio de estado, pero también queremos que
Pedidono conozca a ninguno de ellos. - Queremos añadir nuevos avisos (email, fidelización) fácilmente, pero también queremos no modificar
Pedidocada vez. - Queremos probar
Pedidocon tests unitarios, pero también queremos que en producción se usen los servicios reales de SMS y push (y connewdentro del método, no hay forma de sustituirlos en los tests).
Conclusión
En esta lección hemos establecido los cimientos del curso: un patrón de diseño es una solución con nombre, probada y adaptable a un problema recurrente, siempre descrita mediante cuatro elementos (contexto, problema, solución y consecuencias) y documentada en fichas estandarizadas. Igual de importante es lo que un patrón no es: ni código copiable, ni framework, ni algoritmo. Y con el problema de las notificaciones de PideYa has visto ya el tipo de situación que los patrones resuelven: código que funciona hoy pero que se degrada con cada nuevo requisito.
Quizá te preguntes de dónde salió esta idea de catalogar soluciones con nombre propio. La respuesta es una historia curiosa que empieza, nada menos, en la arquitectura de edificios. Es el tema de la siguiente lección: Historia y Origen de los Patrones de Diseño.
Curso de Patrones de Diseño de Software
Módulo 1: Introducción a los Patrones de Diseño
- ¿Qué son los Patrones de Diseño?
- Historia y Origen de los Patrones de Diseño
- Principios de Diseño: SOLID y Otros Fundamentos
- UML Esencial para Entender Patrones
- Clasificación de los Patrones de Diseño
- Ventajas y Desventajas de Usar Patrones de Diseño
Módulo 2: Patrones Creacionales
- Introducción a los Patrones Creacionales
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparativa y Elección de Patrones Creacionales
Módulo 3: Patrones Estructurales
- Introducción a los Patrones Estructurales
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparativa y Elección de Patrones Estructurales
Módulo 4: Patrones de Comportamiento
- Introducción a los Patrones de Comportamiento
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparativa y Elección de Patrones de Comportamiento
Módulo 5: Aplicación de Patrones de Diseño
- Cómo Seleccionar el Patrón Adecuado
- Ejemplos Prácticos de Uso de Patrones
- Patrones de Diseño en Proyectos Reales
- Refactorización Usando Patrones de Diseño
- Antipatrones: Cuándo los Patrones se Vuelven un Problema
Módulo 6: Patrones de Diseño Avanzados
- Patrones de Diseño en Arquitecturas Modernas
- Patrones de Diseño en Microservicios
- Patrones de Diseño en Sistemas Distribuidos
- Patrones de Concurrencia
- Patrones de Diseño en Desarrollo Ágil
