Cerramos el módulo con una pregunta que no va de técnica sino de proceso: si el equipo de PideYa trabaja en sprints de dos semanas, entregando en cada uno la funcionalidad mínima que aporta valor, ¿cuándo se diseña? ¿Se dibujan los patrones por adelantado, o se dejan aparecer? La tensión entre diseñar bien y entregar pronto es tan vieja como la agilidad misma, y los patrones están justo en el centro: son la herramienta perfecta de diseño... y la tentación perfecta de sobreingeniería (lo vimos en 05-05). Esta lección enseña a resolver esa tensión: cómo emergen los patrones del ciclo TDD, cómo hacen el código testeable, cuándo apostar por uno en mitad de un sprint y cómo se convierten en el lenguaje del equipo.
Contenido
- Diseño emergente vs. Big Design Up Front
- TDD y patrones: el refactor como cuna
- Patrones que facilitan testear
- YAGNI vs. anticipación: cuándo apostar
- Patrones como lenguaje del equipo: revisiones y ADRs
- Caso: una historia de usuario, tres sprints
Diseño emergente vs. Big Design Up Front
El Big Design Up Front (BDUF) diseña toda la arquitectura antes de escribir código: diagramas de todos los patrones, todas las interfaces, todos los casos. Su problema no es diseñar — es diseñar con la información de peor calidad: al principio del proyecto es cuando menos sabes del dominio. El BDUF de PideYa habría diseñado un Abstract Factory de pasarelas para ocho países... y luego el negocio solo abrió en dos.
El diseño emergente invierte la apuesta: se escribe lo mínimo que resuelve la historia actual, con tests, y el diseño se refina continuamente mediante refactorización. Los patrones no se colocan: se descubren cuando el código los pide. Cuidado con la caricatura: diseño emergente no significa "sin diseño" — significa diseño continuo, con criterio en cada paso. Las cuatro reglas del diseño simple de Kent Beck ordenan ese criterio: (1) pasan los tests, (2) revela la intención, (3) sin duplicación, (4) lo más pequeño posible.
| BDUF | Diseño emergente | |
|---|---|---|
| Cuándo se decide un patrón | Antes de codificar | Cuando el código lo pide |
| Riesgo típico | Patrones para futuros que no llegan (patternitis) | Deuda si no se refactoriza de verdad |
| Información disponible | Mínima | Máxima (código y tests reales delante) |
| Requiere | Acertar el futuro | Disciplina de refactorización y tests |
TDD y patrones: el refactor como cuna
El ciclo TDD — red (test que falla) → green (código mínimo que pasa) → refactor (mejorar sin cambiar comportamiento) — tiene un momento exacto donde nacen los patrones: el refactor. En green está prohibido ser listo; en refactor, con la red de tests tendida, miras el código y aplicas el método de 05-01: ¿qué fuerza está doliendo? ¿qué patrón la resuelve?
Ejemplo real del equipo de PideYa. Sprint N, historia "cobrar con tarjeta": en green basta una clase CobroTarjeta. Sprint N+1, historia "cobrar con Bizum": el segundo test lleva a un if (metodo == BIZUM). Sprint N+2, "cobrar con PayPal": tercer if. Ahora el código duele (regla 3: duplicación del esqueleto de cobro) y el refactor extrae la interfaz MetodoCobro — el Strategy de 04-10 ha emergido, con tres implementaciones reales y ni una especulativa.
La otra mitad del vínculo la vimos en 05-04: cuando el código no tiene tests, los tests de caracterización (fijar el comportamiento actual antes de tocar nada) son el prerequisito para refactorizar hacia patrones. TDD te regala esa red desde el principio; la caracterización la reconstruye a posteriori. En ambos casos la secuencia es la misma: red de tests primero, patrón después.
Patrones que facilitan testear
La relación es bidireccional: TDD hace emerger patrones, y ciertos patrones hacen posible el TDD. Los tres mecanismos, con sus dobles de prueba en JUnit:
1. DIP + inyección → dobles de prueba. Si FachadaCheckout recibe sus puertos por constructor (06-01), el test los sustituye por mocks:
@Test
void checkoutRechazadoSiElCobroFalla() {
PasarelaPago pasarela = mock(PasarelaPago.class); // doble del puerto
Notificador notificador = mock(Notificador.class);
when(pasarela.cobrar(any(), any()))
.thenReturn(ResultadoCobro.rechazado("fondos insuficientes"));
var checkout = new FachadaCheckout(pasarela, notificador); // inyección: la costura
assertThrows(CobroRechazadoException.class, () -> checkout.procesar(pedidoDePrueba()));
verify(notificador, never()).notificar(any()); // no se notifica lo que no se cobró
}Sin la inyección, FachadaCheckout haría new AdaptadorRedsys() por dentro y el test necesitaría... una cuenta de Redsys. La interfaz inyectada es el punto donde el test entra.
2. Strategy → fakes. Un fake es un doble con lógica real simplificada. La PasarelaEnMemoria de 06-01 es un fake del puerto de pagos: cobra "de verdad" contra un mapa en memoria, y sirve para tests de integración rápidos sin mockear cada llamada. Toda Strategy admite una estrategia-fake: un CalculadorEnvioFijo que siempre devuelve 2,99 € hace deterministas los tests del checkout.
3. Fachadas → costuras de test. Una costura (seam, Michael Feathers) es un punto donde puedes cambiar el comportamiento sin editar el código. FachadaCheckout es la costura perfecta para los tests de la capa web: los tests del controlador mockean la fachada entera y no arrastran el subsistema de checkout; los tests del subsistema atacan la fachada real con fakes en los puertos. Cada frontera bien diseñada (fachada, puerto, estrategia) es un sitio donde un test puede agarrar el sistema.
Regla de diagnóstico: el código difícil de testear es código mal diseñado. Si para probar una clase necesitas base de datos, red y un Singleton mutado (la Singletonitis de 05-05), el test te está gritando qué patrón falta.
YAGNI vs. anticipación: cuándo apostar
YAGNI ("You Aren't Gonna Need It"): no construyas para necesidades futuras hipotéticas. Pero ¿entonces nunca se anticipa nada? La respuesta práctica distingue dos cosas que suelen confundirse:
- Flexibilidad especulativa (cara de añadir, cara de quitar): jerarquías, fábricas y capas "por si acaso". Aquí YAGNI gana casi siempre — quitar un Abstract Factory innecesario cuesta más que añadirlo cuando haga falta.
- Costuras baratas (casi gratis de añadir, valiosísimas después): recibir dependencias por constructor, programar contra interfaces en las fronteras (BD, red, terceros), no dejar
newde infraestructura enterrado en el dominio. Esto no es especular: es no clavar puertas.
Guía para decidir en mitad de un sprint:
| Señal | Decisión |
|---|---|
| La necesidad es de la historia actual | Aplica el patrón ya, sin culpa |
| "Seguramente en algún sprint futuro..." | YAGNI: código simple + costura barata |
| Segunda vez que aparece la variación | Prepara el refactor (véase la regla de tres) |
| Tercera vez | Refactoriza al patrón: ya tienes tres casos reales que lo validan |
| Es una frontera con el exterior (pago, envío, terceros) | Interfaz desde el día 1: el coste es una línea |
La regla de tres (Fowler) es el punto medio operativo entre YAGNI y anticipación: a la tercera repetición, el patrón se paga solo — y es la versión ágil del "espera hasta que duela" de 05-01.
Patrones como lenguaje del equipo
La ventaja más citada de los patrones desde 01-06 — el vocabulario compartido — es en ágil una herramienta de proceso:
- En revisiones de código: "esto pide un Strategy" transmite en cuatro palabras un rediseño completo; "este mediador está engordando" (el mediador-dios de 05-05) es una alarma que todos entienden. El comentario de revisión con nombre de patrón es verificable: el revisado puede ir al catálogo y comprobar si las fuerzas encajan.
- En ADRs ligeros (Architecture Decision Records): un fichero Markdown por decisión, en el propio repo, con cuatro campos. El del circuit breaker de PideYa:
# ADR-014: Circuit breaker en las llamadas a Pagos
Estado: aceptada (sprint 23)
Contexto: dos incidentes de fallo en cascada cuando Pagos degrada (>5 s de respuesta).
Decisión: Resilience4j con umbral 5 fallos / espera 10 s; fallback = encolar cobro diferido.
Consecuencias: checkout responde siempre en <3 s; aparece estado "pago pendiente"
que Soporte debe conocer; alternativa descartada: reintentos sin fusible (amplifica la carga).Diez líneas escritas en diez minutos. Su valor aparece dos años después, cuando alguien pregunta "¿por qué hay un circuit breaker aquí?" y la respuesta no depende de la memoria de nadie. En ágil, donde el diseño se decide en pequeño y a menudo, los ADRs son la memoria del diseño emergente.
Caso: una historia de usuario, tres sprints
Historia del backlog de PideYa: «Como restaurante quiero ofrecer 2x1 en pizzas los martes para atraer pedidos entre semana». Veamos el diseño evolucionar:
Sprint 1 — lo simple que funciona. TDD: test dosPizzasElMartesCobraUna(). En green, la solución mínima es un descuento codificado en el cálculo del total: un if (esMartes() && ...). En refactor se extrae a un método con nombre (aplicarPromocionMartes), y ahí se para. Cero patrones. Correcto: una promoción no justifica una arquitectura. Se anota la costura: el cálculo del total ya recibe el reloj inyectado (Clock), porque si no, el test del martes solo pasaría los martes — la costura barata que YAGNI sí permite.
Sprint 2 — segunda variación. Nueva historia: "20 % en sushi los domingos". Segundo if. La regla de tres dice: aún no, pero prepara el terreno — los tests de ambas promociones se organizan para que el refactor inminente no los rompa (prueban el resultado, no la estructura interna).
Sprint 3 — el patrón emerge. Tercera historia: "envío gratis a partir de 30 € el primer pedido". Tres promociones, tres condiciones, tres efectos: el código duele de forma reconocible. Refactor del ciclo: emerge Promocion como interfaz con implementaciones por promoción — Strategy — y las promociones activas se aplican en cadena sobre el pedido. En la revisión de código alguien señala que si el negocio quiere que los restaurantes escriban sus propias reglas, el siguiente paso sería el Interpreter de reglas de promociones que ya construimos en 04-04. Se escribe un ADR de cinco líneas: "Strategy ahora; Interpreter solo si la historia de 'reglas configurables por restaurante' entra al backlog". El patrón grande queda documentado y aplazado: eso es diseñar con YAGNI, no contra él.
El resultado a vista de tres sprints: el mismo destino al que un BDUF habría querido saltar el primer día — pero llegando con tres promociones reales que validan el diseño, tests que lo protegen y ni una abstracción de más por el camino.
Errores Comunes y Consejos
- Confundir diseño emergente con no diseñar: saltarse sistemáticamente la fase de refactor "porque hay prisa" acumula los
ifpara siempre. El refactor no es opcional: es donde ocurre el diseño. - BDUF disfrazado de sprint 0: dedicar tres sprints a "montar la arquitectura" con todos los patrones antes de la primera historia. Entrega la primera historia sobre lo mínimo y deja que la arquitectura crezca con las siguientes.
- Mockear lo que no es tuyo... ni es frontera: tests con siete mocks encadenados que se rompen con cada refactor. Mockea en las costuras (puertos, fachadas), usa objetos reales en el dominio puro — que por ser puro (inmutable, sin I/O, lección 06-04) no necesita dobles.
- Usar YAGNI como excusa para clavar puertas: negarse a inyectar una dependencia "porque YAGNI" confunde flexibilidad especulativa con costura barata. La interfaz en la frontera cuesta una línea; el
newenterrado cuesta un sprint de desenterrar. - Decisiones de patrón sin rastro: el patrón se aplicó, el porqué se olvidó, y dos años después alguien lo des-refactoriza (05-05) porque "sobraba". Diez minutos de ADR lo evitan.
- Imponer el patrón en la revisión sin las fuerzas: "yo aquí pondría un Visitor" sin señalar qué duele es opinión, no revisión. Nombra la fuerza (duplicación, frontera, variación) y entonces el patrón.
Ejercicios
- Clasifica las anticipaciones. Para cada decisión en el sprint 1 de una funcionalidad nueva de PideYa ("propinas al repartidor"), di si es costura barata (hazla) o flexibilidad especulativa (YAGNI): (a) interfaz
CalculadorPropinacon una sola implementación de porcentaje fijo; (b) recibir el repositorio de propinas por constructor; (c) jerarquía de eventosPropinaAnadida/PropinaModificada/PropinaCanceladacon Event Sourcing "porque los pedidos ya lo usan"; (d)Clockinyectado para el timestamp de la propina. - Haz testeable la clase.
GeneradorFacturasllama internamente aConfiguracionPideYa.getInstancia().getIvaVigente()y anew ClienteHacienda().enviar(factura). Explica por qué es difícil de testear y refactorízala (firma de la clase y test de ejemplo) usando los patrones de esta lección. - Escribe el ADR. El equipo decide en el sprint 3 del caso 2x1 aplicar Strategy y aplazar Interpreter. Redacta el ADR completo (título, estado, contexto, decisión, consecuencias) en menos de 12 líneas.
Soluciones
- (a) Frontera dudosa: si el cálculo es una regla interna estable, el interface con una sola implementación es especulativo — YAGNI, extráelo cuando llegue la segunda variante (regla de tres); es defendible solo si ya se sabe que porcentaje/fijo/redondeo entra el sprint que viene. (b) Costura barata: hazla — es una línea y sin ella no hay test unitario posible. (c) Flexibilidad especulativa clara: tres tipos de evento y Event Sourcing para una funcionalidad que aún no tiene ni una escritura real; YAGNI. (d) Costura barata: sin
Clockinyectado los tests dependen de la hora real — hazla. - Es difícil de testear porque el Singleton acopla al estado global (no puedes variar el IVA por test sin mutarlo — Singletonitis) y el
newinterno obliga a hablar con Hacienda de verdad. Refactor:public GeneradorFacturas(ConfiguracionFiscal config, EnvioFacturas envio)— dos puertos inyectados (DIP);ConfiguracionFiscalpuede implementarla la configuración real y un fake de test;EnvioFacturasla implementanClienteHacienday un mock. Test:var gen = new GeneradorFacturas(configConIva(21), mock(EnvioFacturas.class)); var f = gen.generar(pedidoDe(100)); assertEquals(new BigDecimal("121.00"), f.total());— sin red, sin estado global, determinista. - Ejemplo válido:
# ADR-021: Strategy para promociones/Estado: aceptada (sprint 3)/Contexto: tres promociones (2x1 martes, 20 % sushi domingos, envío gratis >30 €) implementadas como ifs en el cálculo del total; duplicación y riesgo de regresión al añadir la cuarta./Decisión: interfaz Promocion con una implementación por promoción, aplicadas en cadena sobre el pedido; alta de promociones por configuración./Consecuencias: añadir promoción = una clase + un test, sin tocar el checkout; descartado por ahora Interpreter de reglas (04-04): solo se adoptará si entra al backlog la historia "reglas configurables por restaurante"; los tests existentes de las tres promociones se conservan como red de seguridad.
Conclusión
Los patrones y la agilidad no compiten: se necesitan. El diseño emergente sin catálogo es dar vueltas reinventando soluciones con peores nombres; el catálogo sin diseño emergente es patternitis planificada. El equilibrio que practica el equipo de PideYa cabe en cuatro hábitos: dejar que los patrones nazcan en el refactor del ciclo TDD, mantener costuras baratas en las fronteras aunque YAGNI recorte todo lo demás, aplicar la regla de tres antes de abstraer, y dejar por escrito — en una revisión, en un ADR de diez líneas — por qué cada patrón está donde está.
Con esta lección se cierra el módulo 6 y, con él, el recorrido técnico del curso: los veintitrés patrones del GoF construidos pieza a pieza sobre PideYa, los criterios para elegirlos y no abusar de ellos, y el catálogo moderno — arquitecturas, microservicios, distribución, concurrencia y proceso ágil — donde reconociste una y otra vez las mismas intenciones estiradas sobre problemas nuevos. Esa es quizá la lección de fondo: los catálogos crecen, pero las intenciones permanecen, y quien las domina aprende cada patrón nuevo en minutos. El viaje de aprendizaje, en cambio, no termina aquí: en el último módulo reunimos los mejores recursos para continuarlo — empezando por Libros Recomendados.
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
