Los patrones de diseño se comunican con diagramas: la sección "Estructura" de cualquier catálogo es un diagrama de clases, y las colaboraciones entre objetos se muestran con diagramas de secuencia. Si no lees estos diagramas con fluidez, cada patrón te costará el doble. La buena noticia: no necesitas dominar UML entero (que tiene 14 tipos de diagramas); para este curso bastan dos —clases y secuencia— y un puñado de relaciones. En esta lección aprenderás exactamente ese subconjunto, verás cómo lo dibujaremos con mermaid en el resto del curso, y practicarás con las clases de PideYa que ya conoces. No es un curso de UML: es tu kit de supervivencia para leer patrones.
Contenido
- Qué es UML y qué parte necesitamos
- El diagrama de clases: clases, atributos y métodos
- Interfaces y clases abstractas
- Las seis relaciones que debes reconocer
- El diagrama de secuencia básico
- Cómo dibujaremos todo esto en el curso: mermaid
Qué es UML y qué parte necesitamos
UML (Unified Modeling Language) es la notación estándar (desde 1997) para dibujar sistemas orientados a objetos. Es enorme, pero los catálogos de patrones usan una fracción mínima:
| Diagrama | Qué muestra | Para qué lo usan los patrones |
|---|---|---|
| De clases | Estructura estática: clases, interfaces y sus relaciones | La sección "Estructura" de cada patrón |
| De secuencia | Interacción dinámica: qué mensajes se envían los objetos y en qué orden | La sección "Colaboraciones" |
Todo lo demás (casos de uso, actividades, estados, despliegue...) queda fuera de este curso. Regla de oro al leer diagramas de patrones: son esquemáticos, no planos de construcción. Muestran los participantes y relaciones esenciales, omitiendo getters, constructores y detalles accesorios.
El diagrama de clases: clases, atributos y métodos
Una clase se dibuja como una caja con tres compartimentos: nombre, atributos y métodos.
classDiagram
class Pedido {
-Long id
-List~LineaPedido~ lineas
-EstadoPedido estado
+calcularTotal() double
+anadirLinea(LineaPedido linea) void
+cambiarEstado(EstadoPedido nuevo) void
}
Cómo se lee cada línea:
- Visibilidad (el símbolo inicial):
+público,-privado,#protegido,~de paquete. En los diagramas de patrones casi siempre verás+en métodos y-en atributos: la encapsulación estándar. - Atributos:
-Long idsignifica "atributo privadoidde tipoLong". En mermaid los genéricos se escriben con~:List~LineaPedido~esList<LineaPedido>. - Métodos:
+calcularTotal() doublees un método público sin parámetros que devuelvedouble. El tipo de retorno va al final (en UML clásico se escribe: double; mermaid lo admite sin los dos puntos). - Un miembro subrayado es estático; en mermaid se marca con
$al final:+getInstancia()$ Pedido. Uno en cursiva es abstracto; en mermaid, con*:+cocinar()*.
En Java, esa caja corresponde a:
public class Pedido {
private Long id;
private List<LineaPedido> lineas;
private EstadoPedido estado;
public double calcularTotal() { /* ... */ return 0; }
public void anadirLinea(LineaPedido linea) { /* ... */ }
public void cambiarEstado(EstadoPedido nuevo) { /* ... */ }
}Interfaces y clases abstractas
Los patrones viven de las abstracciones ("programa contra una interfaz", como vimos en la lección anterior), así que distinguirlas en un diagrama es vital:
- Una interfaz se marca con el estereotipo
<<interface>>sobre el nombre. - Una clase abstracta se marca con
<<abstract>>(o el nombre en cursiva, en UML clásico).
classDiagram
class Notificador {
<<interface>>
+notificar(Cliente cliente, String mensaje) void
}
class MetodoPagoBase {
<<abstract>>
-String comercioId
+procesar(double importe)* ResultadoPago
+registrarIntento() void
}
Diferencia práctica al leer un patrón: una interfaz solo declara contrato; una clase abstracta puede además aportar código común a sus hijas (como registrarIntento() arriba) dejando abstracto lo variable (procesar, marcado con *). Muchos patrones se apoyan justo en esa combinación.
Las seis relaciones que debes reconocer
Aquí está el 80% del valor de esta lección. Toda la "gramática" de los diagramas de patrones son estas seis flechas:
| Relación | Significado | Notación UML | En mermaid | Ejemplo PideYa |
|---|---|---|---|---|
| Herencia | "es un" (extiende una clase) | Flecha con triángulo vacío hacia el padre | `< | --` |
| Implementación | "cumple el contrato de" una interfaz | Triángulo vacío con línea discontinua | `< | ..` |
| Asociación | "conoce a" (referencia duradera) | Línea continua (opcionalmente con flecha) | --> |
Pedido conoce a Cliente |
| Agregación | "tiene un" (todo-parte débil: las partes sobreviven al todo) | Rombo vacío en el lado del todo | o-- |
Repartidor agrupa Vehiculo (el vehículo existe sin él) |
| Composición | "está compuesto de" (todo-parte fuerte: las partes mueren con el todo) | Rombo relleno en el lado del todo | *-- |
Pedido se compone de LineaPedido |
| Dependencia | "usa temporalmente" (parámetro, variable local, creación) | Flecha discontinua | ..> |
GeneradorFacturas usa Pedido como parámetro |
Y así se ven todas juntas sobre clases de PideYa:
classDiagram
class Notificador {
<<interface>>
+notificar(Cliente c, String msg) void
}
class MetodoPagoBase {
<<abstract>>
+procesar(double importe)* ResultadoPago
}
MetodoPagoBase <|-- PagoTarjeta : herencia
Notificador <|.. NotificadorSms : implementación
Pedido --> Cliente : asociación
Repartidor o-- Vehiculo : agregación
Pedido *-- LineaPedido : composición
GeneradorFacturas ..> Pedido : dependencia
Claves para no confundirlas:
- El triángulo siempre apunta a la abstracción (padre o interfaz). Línea continua = herencia de clase; discontinua = implementación de interfaz.
- El rombo va en el lado del "todo", no de la parte. Para distinguir agregación de composición pregúntate: si borro el todo, ¿tiene sentido que la parte siga existiendo? Si el pedido se cancela y borra, sus líneas no significan nada sueltas → composición (rombo relleno). Si el repartidor se da de baja, la moto sigue existiendo en la flota → agregación (rombo vacío).
- Asociación frente a dependencia: asociación es un atributo (referencia que se conserva); dependencia es un uso puntual (parámetro o variable local). En Java:
private Cliente cliente;es asociación;generarPdf(Pedido p)es dependencia. - Las asociaciones pueden llevar multiplicidad:
"1" --> "0..*"se lee "un pedido tiene de cero a muchas líneas". En mermaid:Pedido "1" *-- "1..*" LineaPedido.
La correspondencia en Java de las tres relaciones "de tener":
public class Pedido {
private Cliente cliente; // Asociación
private final List<LineaPedido> lineas = new ArrayList<>(); // Composición:
// las líneas se crean dentro del pedido y nadie más las referencia
public void anadirLinea(Plato plato, int cantidad) {
lineas.add(new LineaPedido(plato, cantidad)); // nacen y mueren con él
}
}
public class Repartidor {
private Vehiculo vehiculo; // Agregación:
public void asignarVehiculo(Vehiculo v) { this.vehiculo = v; } // viene de fuera
}Matiz honesto: en Java la diferencia agregación/composición no la impone el lenguaje (ambas son referencias); es una intención de diseño sobre ciclo de vida y propiedad. En los diagramas de patrones no suele ser crítica: si dudas, léela como "tiene un".
El diagrama de secuencia básico
El diagrama de clases dice quién es quién; el de secuencia dice qué pasa cuando el sistema se ejecuta: qué objetos participan, qué mensajes (llamadas a métodos) se envían y en qué orden temporal (el tiempo fluye hacia abajo).
Escenario PideYa: un cliente confirma su carrito y el sistema cobra y notifica.
sequenceDiagram
participant C as Cliente
participant SP as ServicioPedidos
participant MP as MetodoPago
participant N as Notificador
C->>SP: confirmar(carrito)
activate SP
SP->>SP: crearPedido(carrito)
SP->>MP: procesar(importe)
activate MP
MP-->>SP: ResultadoPago
deactivate MP
SP->>N: notificar(cliente, "Pedido confirmado")
SP-->>C: pedidoConfirmado
deactivate SP
Elementos que debes reconocer:
- Participantes (arriba): los objetos involucrados. De cada uno cuelga su línea de vida vertical.
- Mensaje síncrono (
->>, flecha continua): una llamada a método; el emisor espera la respuesta. - Respuesta (
-->>, flecha discontinua): el valor devuelto. - Barra de activación (el rectángulo sobre la línea de vida,
activate/deactivate): el intervalo en que ese objeto está ejecutando algo. - Automensaje (
SP->>SP): el objeto llama a un método propio.
En los patrones, el diagrama de secuencia responde a la pregunta que el de clases no puede responder: "vale, estas son las clases... pero ¿quién llama a quién y cuándo?". Verás que en varios patrones la estructura de clases es casi idéntica y lo que los distingue es la secuencia; por eso conviene leer ambos siempre.
Cómo dibujaremos todo esto en el curso: mermaid
En este curso todos los diagramas están escritos en mermaid, una notación de texto que se renderiza como diagrama. Ventaja para ti: puedes copiar cualquier diagrama del curso, pegarlo en mermaid.live y modificarlo para experimentar. La chuleta completa de lo que usaremos:
classDiagram %% inicia un diagrama de clases
class MiClase {
<<interface>> %% o <<abstract>>
-tipo atributoPrivado
+metodo(Tipo param) TipoRetorno
+metodoAbstracto()* Tipo %% * = abstracto
+metodoEstatico()$ Tipo %% $ = estático
}
Padre <|-- Hija %% herencia
Interfaz <|.. Implementacion %% implementación
A --> B : conoce %% asociación (con etiqueta opcional)
Todo o-- Parte %% agregación
Todo *-- Parte %% composición
Usuario ..> Usado %% dependencia
A "1" --> "0..*" B %% multiplicidad
sequenceDiagram %% inicia un diagrama de secuencia
participant A as NombreLegible
A->>B: llamada(args) %% mensaje síncrono
B-->>A: respuesta %% retorno
activate B %% empieza activación
deactivate B %% termina activaciónCon esta chuleta puedes leer el 100% de los diagramas de los módulos 2 a 6. Cuando en el módulo 2 veas la estructura de un patrón, ya no estarás descifrando flechas: estarás leyendo diseño.
Errores Comunes y Consejos
- Confundir el sentido del triángulo de herencia. El triángulo toca a la abstracción (padre/interfaz). En mermaid,
MetodoPagoBase <|-- PagoTarjetase lee "PagoTarjeta hereda de MetodoPagoBase". Si lo lees al revés, entenderás todos los patrones del revés. - Confundir el lado del rombo. El rombo va pegado al todo (el contenedor), no a la parte.
Pedido *-- LineaPedido: el rombo está enPedido. - Intentar que el diagrama lo diga todo. Un diagrama de patrón es un esquema: si no aparecen los getters o el constructor, no es que no existan; es que no importan para entender el patrón. No los añadas al dibujar los tuyos.
- Ignorar la diferencia entre línea continua y discontinua. Continua = relación fuerte y estructural (herencia, asociación); discontinua = relación más débil o de contrato (implementación, dependencia). Este matiz cambia el significado del diagrama.
- Leer solo el diagrama de clases y saltarse el de secuencia. Varios patrones comparten estructura estática casi idéntica; la diferencia está en la dinámica. Acostúmbrate desde ya a leer los dos.
- Consejo: practica el camino inverso. Toma una clase pequeña de un proyecto tuyo y dibújala en mermaid con sus relaciones reales. Dibujar es lo que fija la notación; leer solo, no.
Ejercicios
Ejercicio 1: de diagrama a Java
Traduce este diagrama a esqueletos de clases Java (sin implementar los cuerpos):
classDiagram
class Promocion {
<<interface>>
+calcularDescuento(Pedido pedido) double
}
Promocion <|.. PromocionPrimeraCompra
CalculadoraDescuentos ..> Promocion
CalculadoraDescuentos ..> Pedido
Pedido "1" *-- "1..*" LineaPedido
Ejercicio 2: de Java a diagrama
Dibuja en mermaid (classDiagram) las clases y relaciones de este código, eligiendo bien entre asociación, agregación, composición y dependencia:
public class Restaurante {
private final Carta carta = new Carta(); // creada y poseída por el restaurante
private List<Repartidor> repartidoresFavoritos; // se asignan desde la flota común
public Factura facturarMes(GeneradorFacturas generador) {
return generador.generarMensual(this);
}
}Ejercicio 3: leer una secuencia
Observa el diagrama de secuencia de la sección 5 y responde: (a) ¿qué objeto orquesta el proceso?, (b) ¿en qué momento está activo MetodoPago y qué devuelve?, (c) ¿el mensaje a Notificador espera respuesta según el diagrama? ¿Qué mensaje es un automensaje?
Soluciones
Solución 1:
public interface Promocion {
double calcularDescuento(Pedido pedido);
}
public class PromocionPrimeraCompra implements Promocion { // <|.. implementación
public double calcularDescuento(Pedido pedido) { return 0; }
}
public class CalculadoraDescuentos {
// ..> dependencia: usa Promocion y Pedido como parámetros, no como atributos
public double calcular(Pedido pedido, Promocion promocion) { return 0; }
}
public class Pedido {
// *-- composición 1 a 1..*: el pedido posee sus líneas (al menos una)
private final List<LineaPedido> lineas = new ArrayList<>();
}
public class LineaPedido { }Solución 2:
classDiagram
Restaurante "1" *-- "1" Carta : composición
Restaurante o-- Repartidor : agregación
Restaurante ..> GeneradorFacturas : dependencia
Restaurante ..> Factura : dependencia
Razonamiento: la Carta la crea y posee el restaurante (muere con él) → composición. Los repartidores existen fuera del restaurante y solo se le asocian como favoritos → agregación (una asociación simple --> también sería defendible; lo importante es descartar la composición). GeneradorFacturas y Factura solo aparecen como parámetro y valor de retorno de un método → dependencias.
Solución 3: (a) ServicioPedidos: recibe la petición del cliente y llama a todos los demás. (b) MetodoPago está activo solo entre la llamada procesar(importe) y su respuesta; devuelve un ResultadoPago. (c) Tal como está dibujado, a notificar(...) no se le dibuja flecha de retorno: el diagrama no muestra respuesta (se lee como llamada cuyo resultado no interesa en este escenario). El automensaje es SP->>SP: crearPedido(carrito): ServicioPedidos invocando un método propio.
Conclusión
Ya tienes el kit de lectura de diagramas que usarás en todo el curso: la caja de clase con visibilidades, los estereotipos <<interface>> y <<abstract>>, las seis relaciones (herencia, implementación, asociación, agregación, composición y dependencia, con sus flechas y rombos bien orientados) y el diagrama de secuencia para la dinámica. Además conoces la sintaxis mermaid exacta con la que están escritos todos los diagramas del curso, lista para copiar y experimentar.
Con las herramientas de lectura en la mano, toca desplegar el mapa: ¿cuántos patrones hay, cómo se organizan y qué hace cada familia? Ese mapa —que es también el itinerario de los módulos 2, 3 y 4— es la próxima lección: Clasificación 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
