Un curso de patrones honesto tiene que terminar aquí: en su lado oscuro. Si un patrón es una solución probada a un problema recurrente, un antipatrón es lo contrario y algo más venenoso — una solución atractiva y recurrente que empeora el problema, y que a menudo llega vestida de buena práctica. Los patrones del catálogo pueden convertirse en antipatrones cuando se aplican sin síntoma, y este módulo entero ha construido las defensas: el método de 05-01, los descartes de 05-02, la disciplina de 05-04. Esta lección las completa con la galería de los clásicos, los malos usos concretos de lo aprendido en el curso, las señales de alarma para detectarlos en revisión de código, y la operación inversa: deshacer un patrón mal aplicado.
Contenido
- Qué es un antipatrón
- La galería clásica: síntoma, causa, remedio
- Malos usos de los patrones del curso
- Señales de alarma en revisión de código
- Des-refactorizar: deshacer un patrón mal aplicado
- Ejercicios y conclusión
- Cierre del módulo
Qué es un antipatrón
El término lo popularizó el libro AntiPatterns (Brown, Malveau, McCormick, Mowbray, 1998), y su definición tiene dos partes obligatorias:
- Una solución recurrente que parece buena y produce consecuencias negativas — no basta con "código malo": el antipatrón seduce, por eso se repite.
- Un remedio documentado — igual que un patrón tiene forma problema→solución, el antipatrón tiene forma trampa→salida.
La segunda parte es la que lo hace útil en equipo: nombrar el antipatrón ("esto es un Golden Hammer") no es un insulto, es un diagnóstico con tratamiento adjunto — el mismo vocabulario compartido de 01-01, aplicado al lado malo del mapa.
La galería clásica
| Antipatrón | Síntoma | Causa típica | Remedio |
|---|---|---|---|
| God Object (objeto-dios) | Una clase enorme que sabe y hace de todo; todo cambio pasa por ella; imposible de testear aislada | Añadir "solo un método más" durante años; miedo a crear clases | Extract Class por responsabilidades; dejar como mucho una Facade que orquesta (la secuencia de 05-04) |
| Spaghetti Code | Flujo imposible de seguir: saltos, flags, métodos kilométricos, todo depende de todo | Crecer sin diseño ni refactorización; prisa crónica | Caracterizar con tests, extraer métodos/clases, introducir costuras; patrones solo después, sobre el código ya legible |
| Golden Hammer (martillo de oro) | La misma solución para todo problema ("todo es un microservicio", "todo lleva Observer") | Dominar una sola herramienta; éxito previo extrapolado | Diagnóstico antes que solución (05-01); aprender la herramienta vecina; revisión cruzada |
| Lava Flow (flujo de lava) | Código muerto o incomprensible que nadie borra "por si acaso"; capas fósiles de intentos antiguos | Miedo a borrar sin tests; pérdida del contexto histórico | Cobertura + borrado valiente (el VCS recuerda); eliminar flags y ramas muertas en cada paso por la zona |
| Poltergeist | Clases efímeras que solo crean o invocan a otras y desaparecen; indirección sin aportación | "Más clases = más OO"; capas de gestión ritual | Inline Class: fusionar el fantasma con quien hace el trabajo real |
| Copy-Paste Programming | El mismo bloque, con variaciones sutiles, en N sitios; los bugs se arreglan en N−1 | Es más rápido copiar que abstraer... la primera semana | Regla de tres → extraer método/clase; si las copias varían por un eje, ese eje pide su patrón (Strategy, Template Method) |
| Magic Numbers / Strings | Literales sin nombre gobernando la lógica (if (estado == 3)) |
Prisa; "ya lo documentaré" | Constantes con nombre, enums; si el literal selecciona conducta, quizá State/Strategy |
| Reinventing the Wheel | Framework casero de logging/eventos/inyección conviviendo con el estándar | Desconocimiento del ecosistema; síndrome "aquí somos especiales" | Usar la pieza estándar (05-03 enseñó a reconocerlas); el código propio, solo para el dominio propio |
Nota la asimetría con el catálogo GoF: los patrones se eligen; los antipatrones se contraen, como los hábitos. Por eso el remedio casi siempre incluye un cambio de proceso (tests, revisión, regla de tres) y no solo un cambio de código.
Malos usos de los patrones del curso
La parte que nos toca de cerca: cada familia del catálogo tiene su forma característica de aplicarse mal. Los cinco casos que más veréis:
Singletonitis: estado global disfrazado
El Singleton es el patrón más fácil de escribir y el más caro de mantener, y ya lo advertimos en 02-02. La enfermedad no es tener un Singleton: es que getInstance() se convierta en la forma normal de obtener dependencias. Síntomas: tests que se contaminan entre sí (estado compartido que sobrevive), imposibilidad de tener dos configuraciones (¿recordáis ConfiguracionPideYa cuando quisimos testear el mercado de México?), dependencias invisibles — la firma del método miente sobre lo que usa. Remedio: inyección de dependencias — la unicidad la gestiona quien compone (el contenedor, el main), no la clase; exactamente lo que Spring hace con su singleton-por-contenedor (05-03).
Patternitis: la sobreingeniería con pedigrí
El caso fundacional del curso: el email de bienvenida "catedral" de 01-06 — AbstractEmailSenderFactory, estrategia de saludo, template de plantillas... para un email que llevaba dos años sin cambiar. La patternitis es el Golden Hammer del que ha estudiado el catálogo: cada problema recibe el patrón, aunque no haya síntoma. Se reconoce porque las abstracciones tienen una sola razón imaginaria de existir ("por si algún día...") y porque el número de ficheros que hay que abrir para entender una función crece sin que crezca lo que la función hace. Remedio: los cinco filtros de la balanza, y la humildad del "espera hasta que duela". Los patrones se ganan, no se plantan — tercera vez que lo escribimos en el curso, porque es la lección que más se olvida.
La Factory de una sola implementación
// El trío ceremonial completo, visto en tantos proyectos reales:
public interface ServicioPedidos { ... }
public class ServicioPedidosImpl implements ServicioPedidos { ... } // la ÚNICA implementación
public class ServicioPedidosFactory {
public static ServicioPedidos crear() {
return new ServicioPedidosImpl(); // ...y siempre esta
}
}Tres ficheros donde bastaba uno. La interfaz-espejo con su Impl y su fábrica solo pagan cuando hay (o hay evidencia inminente de) una segunda implementación, un proxy que interponer o una frontera de módulo real que proteger — si no, es indirección ritual: cada lectura salta por tres ficheros para no ganar nada. Remedio: Inline — clase concreta y new (o inyección directa), y la interfaz se extrae en un minuto con el IDE el día que llegue la segunda implementación (05-04). Que la extracción futura sea barata es precisamente lo que hace innecesaria la extracción especulativa.
Jerarquías Visitor prematuras
El Visitor fue el patrón con la letra pequeña más larga del módulo 4: doble despacho, un método por tipo de nodo en cada visitante, y la jerarquía de nodos congelada (añadir un nodo obliga a tocar todos los visitantes). Montarlo "porque la carta seguramente tendrá muchas operaciones" cuando hay una sola operación — y la jerarquía de nodos aún está creciendo — es comprar el coste exacto en el peor momento: pagas la rigidez sobre el eje que todavía se mueve. Remedio: mientras las operaciones sean una o dos y los nodos cambien, métodos en los nodos o instanceof con pattern matching (Java 21 lo dejó digno); Visitor cuando el eje se invierta de verdad: nodos estables, operaciones brotando.
El mediador-dios
La degeneración anunciada en 04-06: CentralReparto nace para desacoplar colegas y, protocolo a protocolo, absorbe la lógica de negocio de todos ellos — 900 líneas donde cocina, repartidores y pedidos son marionetas sin conducta propia. Es el God Object con documento de identidad de patrón, y por eso es más peligroso: sobrevive a las revisiones ("es un Mediator, está en el libro"). Señal de corte: el mediador debe coordinar conversaciones, no ejecutar trabajos — si tiene reglas que pertenecen conceptualmente a un colega, devolvédselas; si sus criterios varían, extraedlos a estrategias inyectadas (como la EstrategiaAsignacion).
Señales de alarma en revisión de código
La checklist para el revisor — cada señal es una pregunta que hacer, no un veredicto automático:
- El nombre del patrón sin su intención: un
*Factorycon una sola cosa que fabricar, un*Manager/*Helperque no sabe decir qué gestiona, un Observer con exactamente un observador previsto para siempre. - Abstracciones con población uno: interfaz con una implementación, jerarquía con una hoja, parámetro de estrategia que siempre recibe la misma. Pregunta: "¿cuál es la segunda variante, y cuándo llega?"
- Indirección sin aduana: si al seguir una llamada atraviesas tres clases que solo delegan sin añadir decisión, validación ni traducción — huele a Poltergeist con vocabulario GoF.
- El diff desproporcionado: la feature era "añadir un campo al ticket" y el PR trae dos interfaces, una fábrica y un builder. Pregunta: "¿qué síntoma actual paga esta estructura?"
- Tests que pelean con el diseño: si testear obliga a resetear singletons, a mocks de mocks, o a arrancar medio sistema — el diseño está gritando; los tests son el primer cliente del código y el mejor detector de antipatrones.
- La justificación en futuro: "esto nos permitirá...", "cuando escalemos...". El código se justifica por síntomas en presente; el futuro se apunta en una nota de intención, no en clases.
- Miedo localizado: "eso mejor no lo toques" señalando una clase. Donde hay miedo hay God Object, Lava Flow o falta de tests — y probablemente las tres cosas.
Des-refactorizar: deshacer un patrón mal aplicado
La operación inversa a 05-04, con la misma disciplina exacta — tests primero, pasos que compilan, commits pequeños — y los movimientos espejados: donde antes extraíamos, ahora inline.
| Patrón sobrante | Operación inversa |
|---|---|
| Factory de una implementación | Inline de la fábrica en los puntos de llamada (new o inyección directa); borrar interfaz si nadie más la usa |
| Strategy con una sola estrategia | Inline del método de la estrategia en el contexto; borrar interfaz y campo |
| Singleton | Introducir el objeto como dependencia inyectada de arriba abajo; getInstance() queda como adaptador deprecado hasta que muera el último uso (la migración gradual de 05-04, en espejo) |
| Visitor prematuro | Mover cada visitX como método al nodo correspondiente (o a un switch con pattern matching); borrar accept |
| Mediador-dios | Devolver cada regla al colega dueño (Move Method); el mediador queda con el enrutado puro — que era su trabajo |
| Decorator/capa ceremonial única | Inline de la capa en el componente; conservar solo si controla, traduce o añade de verdad |
Ejemplo mínimo, deshaciendo la fábrica ceremonial en tres commits:
// Commit 1: la fábrica delega... en nada que merezca existir. Marcarla:
@Deprecated // usar new ServicioPedidos() o inyección — ver PR-1841
public class ServicioPedidosFactory { ... }
// Commit 2..n: cada punto de llamada, migrado y compilando:
ServicioPedidos servicio = new ServicioPedidos(repositorio, eventos); // antes: Factory.crear()
// Commit final: borrar ServicioPedidosFactory y fusionar interfaz + Impl
// (Rename de ServicioPedidosImpl → ServicioPedidos con el IDE: cero riesgo).La regla psicológica importa tanto como la técnica: deshacer un patrón no es admitir un error vergonzoso — es la misma ingeniería que lo puso, aplicada a información nueva (el eje de variación imaginado nunca llegó). Un equipo que sabe des-refactorizar aplica patrones con menos miedo, porque ninguna decisión es una cadena perpetua.
Errores Comunes y Consejos
- Usar "antipatrón" como arma arrojadiza en revisión. El diagnóstico incluye remedio y contexto o no es diagnóstico; "esto es Singletonitis, propongo inyectar X e Y" construye — "esto es un antipatrón" a secas, no.
- Sobrecorregir: pasar de la patternitis al "aquí no se usa ningún patrón" es el mismo Golden Hammer con el martillo contrario. El criterio sigue siendo el síntoma, en ambas direcciones.
- Confundir antipatrón con contexto distinto: un Singleton en un script de migración de una tarde no es Singletonitis; un
switchde tres casos estables no pide Strategy. Los remedios se aplican donde hay síntoma, no donde hay parecido. - Des-refactorizar sin red: quitar estructura rompe exactamente igual que ponerla. Tests de caracterización también aquí.
- Consejo: en los PRs que introduzcan un patrón, pedid una línea de justificación con el síntoma ("segundo tipo de contrato, tercer
ifduplicado"). Cuesta diez segundos y filtra el 90% de la patternitis antes de nacer. - Consejo: mantened una pequeña "galería local" de antipatrones del propio proyecto (con enlaces a los PRs que los deshicieron): enseña más que cualquier libro, porque los ejemplos son de casa.
Ejercicios
Ejercicio 1: diagnóstico en la galería
Clasifica cada caso con su antipatrón (clásico o de los cinco del curso) y esboza el remedio en una frase:
UtilidadesPedidotiene 74 métodos estáticos; aparece importada en 180 ficheros y hay tres versiones decalcularTotalcon resultados ligeramente distintos.- Para leer una propiedad de configuración:
ConfigReaderFactory.getInstance().createReader().getConfig().getValue("timeout")— cuatro clases, ninguna con lógica. - El equipo resolvió bien las notificaciones con Bridge en el módulo 3; desde entonces, las tres últimas features han entrado como "un Bridge nuevo", incluida una donde solo había una dimensión.
- En
GestorDescuentoshay un bloque comentado de 200 líneas con la nota "// sistema antiguo de cupones — NO BORRAR (¿se usa aún en México?)", de 2024.
Ejercicio 2: la revisión del PR
Llega un PR titulado "Preparar exportación de informes para el futuro": añade ExportadorInforme (interfaz), ExportadorInformeCsvImpl (única implementación), FabricaExportadores (devuelve siempre la anterior) y GestorExportacion (llama a la fábrica y delega). El único requisito del sprint era "exportar el informe de cierre a CSV". Escribe (a) las señales de alarma concretas de la checklist que aplican, (b) el comentario de revisión que dejarías — constructivo, con remedio y con el criterio de cuándo SÍ merecería la pena esa estructura.
Ejercicio 3: plan de des-refactorización
ConfiguracionPideYa sigue siendo un Singleton clásico (getInstance()) usado desde 23 clases, y los tests de integración se pisan la configuración unos a otros. Diseña el plan de des-refactorización gradual: orden de los pasos, cómo conviven getInstance() y la inyección durante la migración, qué red de seguridad usas y cuál es la condición de borrado final.
Soluciones
Solución 1: (1) God Object (en su variante "clase de utilidades") con Copy-Paste dentro (tres calcularTotal): extraer por responsabilidades hacia los objetos de dominio dueños de cada cálculo, unificar los clones con tests que fijen cuál de los tres comportamientos es el correcto. (2) Poltergeist en cadena: inline de las capas fantasma hasta dejar configuracion.timeout() — una pieza con lógica real (leer y cachear) y ninguna ceremonial. (3) Golden Hammer post-éxito: volver al diagnóstico por fuerzas (05-01) — Bridge paga con dos dimensiones que varían independientemente; con una, es una interfaz normal. (4) Lava Flow: resolver la incógnita (¿México lo usa? — los logs o el equipo de México lo saben en una hora), y borrar; el VCS es la red, el comentario-momia no lo es.
Solución 2: (a) Abstracciones con población uno (interfaz, fábrica de un producto); justificación en futuro ("para el futuro" en el propio título); diff desproporcionado (cuatro clases para un requisito de una); Poltergeist probable en GestorExportacion (solo llama y delega). (b) Comentario tipo: «El requisito se cubre con una clase ExportadorInformeCsv y su test. La interfaz y la fábrica no tienen hoy segunda variante que las justifique — propongo dejarlas fuera y anotar la intención: cuando llegue el segundo formato (¿está en roadmap?), extraer ExportadorInforme con el IDE cuesta un minuto y entonces la fábrica —o el Template Method del informe de cierre, que ya existe— será la estructura correcta con dos casos reales delante. Así el que venga lee una clase, no cuatro.» — nombra el síntoma ausente, da el remedio, fija el criterio de reapertura y no humilla a nadie.
Solución 3: Red: tests de caracterización de los consumos de configuración más delicados + los tests de integración existentes (que además son la motivación: dejarán de pisarse). Pasos: 1. hacer inyectable la clase (constructor público o de factoría, la instancia deja de autogestionarse) manteniendo getInstance() como puente que devuelve una instancia por defecto — verde, commit; 2. migrar las 23 clases por tandas: cada una pasa a recibir ConfiguracionPideYa por constructor (las tandas las marca el grafo: primero las hojas, luego quienes las crean) — commit por tanda; 3. los tests de integración construyen ya su propia configuración por escenario — la contaminación desaparece y lo prueba; 4. @Deprecated getInstance() cuando queden solo usos internos; 5. condición de borrado: cero llamadas a getInstance() fuera del punto de composición (el main/contenedor, único lugar que decide la unicidad a partir de ahora). Es la migración gradual de 05-04 en espejo: construir la vía nueva, migrar por tandas en verde, demoler la vieja al final.
Conclusión
Ya tienes el mapa del lado oscuro: la definición honesta de antipatrón (solución seductora + consecuencias + remedio), la galería clásica del God Object al Lava Flow, y los cinco malos usos del catálogo que este mismo curso podría provocar — Singletonitis, patternitis, fábricas ceremoniales, Visitors prematuros y mediadores-dios — con sus señales de alarma para cazarlos en revisión y la disciplina espejo para deshacerlos sin miedo. La simetría final del módulo es esta: aplicar un patrón y retirarlo son la misma ingeniería, guiada por el mismo juez — el síntoma presente, nunca la moda ni el miedo.
Cierre del módulo
Con esto, el módulo del oficio queda completo: sabes elegir patrón con método y descartes explícitos, has visto el catálogo colaborando a escala real en PideYa, lo reconoces en el JDK, Spring y el código ajeno, sabes refactorizar hacia un patrón con red de seguridad — y ahora también desde él cuando sobra. El catálogo GoF ya no es una lista de 23 fichas: es una caja de herramientas con criterio de uso, que era la promesa del curso.
Pero el GoF se escribió para objetos dentro de un proceso, y el software de 2026 vive repartido: servicios que se llaman por red, hilos que compiten por datos, arquitecturas que imponen sus propios patrones — hexagonal, CQRS, sagas, circuit breakers, productores y consumidores. Muchos te resultarán familiares, porque son las mismas intenciones del catálogo estiradas sobre la red y la concurrencia; otros son fauna nueva con problemas nuevos. Ese es el territorio del módulo que empieza: nos vemos en Patrones de Diseño en Arquitecturas Modernas.
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
