Patrones de Diseño Gang of Four (GoF) en Java
Volver a Artículos
GoFArtículo

Arquitectura de software

Patrones de Diseño Gang of Four (GoF) en Java

Guía completa de los 23 patrones, ejemplos prácticos, SOLID y arquitectura del mundo real

No vas a memorizar 23 nombres: vas a aprender a reconocer los problemas de diseño que hacen útil a un patrón.

Documento de código central conectado a estructuras de patrones de diseño GoF en estilo neón

Lo que promete esta guía

No vas a memorizar 23 nombres. Vas a aprender a reconocer los problemas de diseño recurrentes que hacen útil a un patrón - y las situaciones en las que un patrón solo empeoraría el código.

Edición ampliada basada en el manuscrito original de GoF/Java.

Nota editorial

Este artículo usa el manuscrito original como columna vertebral editorial y lo amplía con contexto adicional de ingeniería de software en general. La estructura original - historia, los 23 patrones de la Gang of Four, los patrones de Java más frecuentes en la práctica, análisis en profundidad, relaciones con SOLID, ejemplos de frameworks, advertencias sobre sobreingeniería, modelos mentales y guías de decisión - se ha preservado en espíritu, mientras que la prosa se reescribió para un público internacional amplio.

Actitud del lector

Siempre que sea posible, mira el problema antes que el patrón. Pregunta qué está cambiando, qué está acoplado, qué está duplicado y qué debería permanecer estable. El nombre del patrón debe venir después.

Índice

  1. Introducción: por qué el diseño importa más que el código que funciona
  2. De la arquitectura al software: la historia detrás de los patrones de diseño
  3. Qué es realmente un patrón de diseño - y qué no es
  4. Las tres familias GoF
  5. El catálogo completo de los 23 patrones GoF
  6. Los patrones que más vas a encontrar en Java
  7. Análisis en profundidad con ejemplos en Java
  8. Cómo reconocer patrones en código existente
  9. Patrones GoF y principios SOLID
  10. Dónde aparecen los patrones en el ecosistema Java
  11. Comparaciones de patrones que evitan errores comunes
  12. Aplicación práctica: refactorizar un checkout de comercio electrónico
  13. Sobreingeniería: cuándo no usar un patrón
  14. Guía de decisión y matriz de selección de patrones
  15. Conclusión

1. Introducción: por qué el diseño importa más que el código que funciona

Imagina dos sistemas que superan sus pruebas y satisfacen igualmente los requisitos de negocio de hoy. En el primero, cada nuevo método de pago obliga a los desarrolladores a modificar un condicional largo, tocar varios controladores, actualizar pruebas en módulos no relacionados y esperar que una integración antigua no se rompa. En el segundo, un nuevo método de pago se introduce añadiendo una implementación detrás de una interfaz estable. El resultado de negocio puede ser idéntico hoy. La diferencia se vuelve visible mañana.

Esa diferencia es el diseño. Un buen diseño no consiste en hacer que el código parezca sofisticado. Consiste en organizar responsabilidades y dependencias de modo que el cambio inevitable tenga un costo controlado. Los patrones de diseño GoF se volvieron importantes porque dieron a los desarrolladores un vocabulario compartido para estructuras que resuelven repetidamente este tipo de problema. Un desarrollador puede decir “Strategy”, “Adapter” o “Facade” y comunicar una intención de diseño que, de otro modo, exigiría párrafos de explicación.

Por lo tanto, un patrón de diseño no es una biblioteca, un framework ni un fragmento de código para copiar. Es una forma reutilizable de pensar sobre un problema de diseño recurrente. El mismo patrón puede verse distinto en Java, C#, Python o TypeScript porque el patrón vive en el nivel de las responsabilidades y la colaboración, no en el de la sintaxis.

Una pregunta útil para llevar a lo largo de este artículo

Si tu sistema recibe cinco variaciones nuevas de la misma regla de negocio el mes que viene, ¿qué clases tendrán que cambiar? La respuesta suele revelar si un patrón podría ayudar.

Para el desarrollador individual, aprender patrones mejora la lectura de código, la refactorización, la comunicación técnica, las entrevistas, las discusiones arquitectónicas y la capacidad de entender frameworks que, de otro modo, parecen “mágicos”. Para equipos y organizaciones el beneficio es mayor: un vocabulario de diseño compartido reduce la ambigüedad, mejora la mantenibilidad, ayuda a aislar dependencias externas y facilita la evolución de sistemas de larga vida.

También existe una dimensión social más amplia. La sociedad moderna depende del software en banca, salud, transporte, comunicación, gobierno, educación e infraestructura. El software mantenible es más fácil de probar, más seguro de cambiar y menos costoso de mantener con vida. Los patrones de diseño no garantizan un buen software, pero el pensamiento disciplinado que hay detrás puede contribuir a sistemas digitales más fiables y a prácticas de ingeniería más sostenibles.

2. De la arquitectura al software: la historia detrás de los patrones de diseño

La idea de “patrón” no empezó en el software. El arquitecto Christopher Alexander y sus colaboradores describieron soluciones recurrentes a problemas de diseño recurrentes en el entorno construido. La idea clave no era prescribir un plano rígido, sino capturar una relación entre contexto, problema, fuerzas y solución para que los profesionales pudieran reutilizar la experiencia acumulada sin copiar un edificio literalmente.

Los ingenieros de software reconocieron una analogía poderosa. Los sistemas orientados a objetos también enfrentaban fuerzas recurrentes: la creación de objetos debía ser flexible, los algoritmos debían ser reemplazables, las interfaces incompatibles debían cooperar, los subsistemas complejos debían simplificarse y los objetos debían comunicarse sin enredarse en una red de dependencias.

En 1994, Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides publicaron Design Patterns: Elements of Reusable Object-Oriented Software. Por los cuatro autores, el libro se conoció como el libro de la Gang of Four, o GoF. Su aportación duradera no fue que cada idea se inventara desde cero. Los autores observaron estructuras recurrentes, las nombraron, documentaron su intención, describieron sus participantes y consecuencias y dieron a los desarrolladores un vocabulario para discutir el diseño orientado a objetos.

El libro catalogó 23 patrones en tres familias: creacionales, estructurales y de comportamiento. Décadas después, los detalles del desarrollo de software han cambiado drásticamente - la computación en la nube, los microservicios, los contenedores, los sistemas reactivos, las plataformas sin servidor, la inyección de dependencias, la programación funcional y el streaming de eventos son hoy habituales. Sin embargo, las preguntas centrales siguen siendo reconocibles: ¿cómo creamos objetos? ¿Cómo componemos comportamiento? ¿Cómo aislamos el cambio? ¿Cómo coordinamos la colaboración? Por eso los patrones GoF siguen siendo útiles.

Línea de tiempo desde los patrones arquitectónicos de Christopher Alexander en 1977 hasta el libro de la Gang of Four en 1994 y las arquitecturas modernas de nube y microservicios
Figura 1: los patrones viajan de la arquitectura al software - lo que cambia es la tecnología, no las fuerzas de diseño recurrentes.

3. Qué es realmente un patrón de diseño - y qué no es

3.1 Un patrón es un modelo de solución

Un patrón describe una disposición de responsabilidades que ha demostrado ser útil repetidamente en cierto contexto. Strategy, por ejemplo, dice que cuando varios algoritmos sirven al mismo propósito y deben variar independientemente del cliente, puedes encapsular esos algoritmos detrás de una abstracción común y hacer que el cliente dependa de esa abstracción. No exige nombres de clase específicos ni un número concreto de clases.

3.2 Un patrón no es código para copiar y pegar

Copiar una implementación de manual sin entender las fuerzas que hay detrás es una de las formas más rápidas de convertir los patrones en complejidad accidental. La implementación debe encajar con el lenguaje, el framework, la escala, la estrategia de pruebas y el dominio. En el Java moderno, un Strategy puede ser una jerarquía de clases, una interfaz funcional implementada con lambdas, una colección de beans de Spring o un mapa de claves de negocio a funciones.

3.3 Un patrón tiene consecuencias

Toda decisión de diseño introduce compromisos. Un patrón puede reducir el acoplamiento mientras aumenta la indirección. Puede mejorar la extensibilidad mientras aumenta el número de tipos. Puede aclarar responsabilidades mientras hace que el flujo de control resulte menos evidente para alguien nuevo. La alfabetización en patrones exige, por tanto, dos habilidades: reconocer cuándo las fuerzas justifican la estructura y reconocer cuándo la estructura sería más pesada que el problema.

3.4 Los patrones como lenguaje compartido

Quizá el mayor valor a largo plazo del GoF sea la comunicación. “Usa un Adapter en la frontera de pago” dice mucho más que “crea otra clase”. El nombre sugiere intención: traducir una interfaz externa a una interna para que el dominio no hable el idioma del proveedor. El vocabulario compartido permite que las discusiones de arquitectura pasen de la sintaxis al diseño.

4. Las tres familias GoF

Las tres familias GoF, la pregunta que responde cada una y los patrones que contiene.
FamiliaPregunta centralPatrones
Creacionales¿Cómo se pueden crear objetos sin acoplar a los clientes a detalles concretos de construcción?Abstract Factory, Builder, Factory Method, Prototype, Singleton
Estructurales¿Cómo se pueden componer clases y objetos en estructuras flexibles?Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
De comportamiento¿Cómo deben distribuirse los algoritmos, las responsabilidades, la comunicación y el estado?Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor

Las categorías son útiles como mapa, pero los diseños reales suelen combinar patrones. Una aplicación puede usar Factory Method para elegir un Strategy, envolver el Strategy con un Decorator, exponer una operación simplificada mediante un Facade, publicar resultados con eventos al estilo Observer y colocar un Adapter alrededor de una API externa. El valor no está en apilar patrones; está en dar a cada problema recurrente una estructura apropiada.

Los 23 patrones GoF agrupados en familias creacionales, estructurales y de comportamiento con la pregunta que responde cada familia
Figura 2: los 23 patrones organizados por la pregunta que responde cada familia - cinco creacionales, siete estructurales y once de comportamiento.

5. El catálogo completo de los 23 patrones GoF

El siguiente catálogo es intencionadamente práctico. Para cada patrón, concéntrate en la presión de diseño que alivia en lugar de intentar memorizar un diagrama UML.

5.1 Abstract Factory (creacional)

Crear familias de objetos relacionados sin atar al cliente a sus clases concretas.

Aplicaciones típicas: Un kit de interfaz con familias de componentes claros y oscuros; una abstracción de nube que crea clientes de almacenamiento, colas y bases de datos para AWS o Azure.

Señal de diseño

Úsalo cuando los productos deban variar juntos como una familia coherente. Evítalo cuando solo varía un producto o cuando introducir un nuevo tipo de producto obligaría a cambios generalizados en las fábricas.

5.2 Builder (creacional)

Construir un objeto complejo paso a paso haciendo explícitos los parámetros opcionales y las reglas de construcción.

Aplicaciones típicas: Solicitudes HTTP, informes, objetos de configuración, consultas, agregados de dominio, DTO con muchos campos opcionales.

Señal de diseño

Úsalo cuando los constructores se vuelven ilegibles, la construcción tiene etapas o la validación corresponde al momento de construir. No lo uses solo para hacer fluido cualquier objeto simple.

5.3 Factory Method (creacional)

Definir una operación de creación cuyo producto concreto puede variar sin que el cliente dependa directamente de clases concretas.

Aplicaciones típicas: Servicios de notificación, analizadores, exportadores, conectores, controladores, clientes de integración.

Señal de diseño

Úsalo cuando las decisiones de creación deban ser extensibles o localizadas. Distínguelo del Simple Factory, que es útil pero no es uno de los 23 patrones formales del GoF.

5.4 Prototype (creacional)

Crear objetos nuevos copiando un prototipo ya configurado en lugar de reconstruirlos desde cero.

Aplicaciones típicas: Objetos gráficos costosos, plantillas configuradas, entidades de simulación, modelos de dominio preconfigurados.

Señal de diseño

Útil cuando la configuración es costosa o la clonación es semánticamente natural. Ten cuidado con las copias superficiales frente a las profundas y con el estado mutable compartido.

5.5 Singleton (creacional)

Garantizar que una clase tenga una única instancia controlada y proporcionar acceso a ella.

Aplicaciones típicas: Un coordinador o recurso realmente único en el proceso cuya unicidad forma parte del requisito.

Señal de diseño

Úsalo con cautela. El acceso global puede ocultar dependencias, perjudicar las pruebas y crear estado global mutable. Los contenedores de inyección de dependencias suelen eliminar la necesidad de Singletons manuales.

5.6 Adapter (estructural)

Convertir la interfaz de un componente en la interfaz que espera otro componente.

Aplicaciones típicas: API heredadas, SDK de terceros, proveedores de pago, clientes de nube, migraciones, integraciones con proveedores.

Señal de diseño

Uno de los patrones de frontera más potentes: impide que tipos externos, modelos de error y convenciones de nombres se filtren al dominio.

5.7 Bridge (estructural)

Separar una abstracción de su implementación para que ambas dimensiones evolucionen de forma independiente.

Aplicaciones típicas: Tipo de informe x formato de salida, mando a distancia x dispositivo, tipo de mensaje x transporte.

Señal de diseño

Úsalo cuando dos dimensiones independientes crearían una explosión combinatoria de subclases.

5.8 Composite (estructural)

Tratar objetos individuales y composiciones de forma uniforme mediante una interfaz común.

Aplicaciones típicas: Archivos y carpetas, menús y submenús, árboles organizativos, árboles de expresión, jerarquías de componentes de interfaz.

Señal de diseño

Ideal para estructuras de árbol donde los clientes deben ejecutar operaciones sin importarles si un nodo es una hoja o un grupo.

5.9 Decorator (estructural)

Añadir responsabilidades dinámicamente envolviendo un objeto que expone la misma abstracción.

Aplicaciones típicas: Registro, caché, compresión, cifrado, autorización, métricas, flujos de E/S de Java.

Señal de diseño

Úsalo cuando los comportamientos deban componerse en muchas combinaciones y la herencia explotaría. Vigila el orden de los decoradores, porque el orden de composición puede afectar a la semántica.

5.10 Facade (estructural)

Proporcionar una interfaz más simple y de nivel superior a un subsistema complejo.

Aplicaciones típicas: Orquestación de checkout, subsistemas de informes, envoltorios de SDK, flujos de incorporación.

Señal de diseño

Úsalo cuando quienes llaman no deban conocer cada servicio interno. Un Facade simplifica el acceso; no exige ocultar todos los componentes internos.

5.11 Flyweight (estructural)

Compartir el estado intrínseco entre muchos objetos lógicamente separados para reducir el uso de memoria.

Aplicaciones típicas: Renderizado de texto, objetos de juego, editores gráficos, poblaciones de objetos muy grandes con estado repetido.

Señal de diseño

Valioso cuando la presión de memoria es real y el estado repetido es sustancial. En los sistemas de negocio habituales se implementa explícitamente con menos frecuencia.

5.12 Proxy (estructural)

Proporcionar un sustituto que controla el acceso a otro objeto manteniendo una interfaz compatible.

Aplicaciones típicas: Carga diferida, control de acceso, caché, invocación remota, interceptación AOP, fronteras transaccionales.

Señal de diseño

A diferencia del Decorator, el Proxy controla principalmente el acceso o el ciclo de vida; el comportamiento añadido es secundario respecto a ese control.

5.13 Chain of Responsibility (de comportamiento)

Pasar una solicitud por una secuencia de manejadores, cada uno de los cuales puede procesarla, enriquecerla, rechazarla o reenviarla.

Aplicaciones típicas: Filtros HTTP, middleware, cadenas de validación, autorización, canalizaciones de eventos.

Señal de diseño

Útil cuando el procesamiento es naturalmente secuencial y los manejadores deben poder componerse de forma independiente.

5.14 Command (de comportamiento)

Encapsular una solicitud o acción como un objeto.

Aplicaciones típicas: Colas de tareas, programación de trabajos, acciones de interfaz, registros de auditoría, reintentos, deshacer/rehacer.

Señal de diseño

Útil cuando las acciones deben almacenarse, aplazarse, reintentarse, registrarse, componerse o revertirse.

5.15 Interpreter (de comportamiento)

Representar una gramática pequeña y evaluar expresiones en esa gramática.

Aplicaciones típicas: DSL simples, filtros, expresiones de reglas, sintaxis de búsqueda.

Señal de diseño

Apropiado para gramáticas pequeñas; los lenguajes grandes suelen necesitar generadores de analizadores o herramientas de parsing especializadas.

5.16 Iterator (de comportamiento)

Recorrer una colección sin exponer su representación interna.

Aplicaciones típicas: Java Collections, estructuras de datos propias, recorrido de árboles.

Señal de diseño

Tan integrado en Java que los desarrolladores lo usan constantemente mediante Iterator y el for mejorado.

5.17 Mediator (de comportamiento)

Centralizar la comunicación entre un conjunto de objetos que, de otro modo, dependerían directamente unos de otros.

Aplicaciones típicas: Controles de interfaz, salas de chat, coordinación de flujos de trabajo, orquestación de módulos.

Señal de diseño

Útil cuando las dependencias entre pares forman un grafo denso. El propio mediador no debe convertirse en un God Object descontrolado.

5.18 Memento (de comportamiento)

Capturar y después restaurar el estado de un objeto sin exponer su representación interna.

Aplicaciones típicas: Deshacer, instantáneas de editor, puntos de control, restauración de estado.

Señal de diseño

Útil cuando se necesita un estado reversible y el encapsulamiento debe permanecer intacto.

5.19 Observer (de comportamiento)

Definir una dependencia uno a muchos para que las partes interesadas sean notificadas cuando algo cambia.

Aplicaciones típicas: Eventos de dominio, escuchas de interfaz, eventos en memoria, notificaciones, actualizaciones reactivas.

Señal de diseño

Potente para desacoplar emisores de reacciones independientes. En sistemas distribuidos, los brokers de mensajes implementan ideas relacionadas de publicación/suscripción, pero añaden preocupaciones de entrega y consistencia.

5.20 State (de comportamiento)

Permitir que un objeto cambie su comportamiento cuando cambia su estado interno, normalmente delegando el comportamiento específico a objetos de estado.

Aplicaciones típicas: Ciclo de vida de pedidos, flujo de documentos, estado de conexión, procesos de aprobación.

Señal de diseño

Útil cuando los condicionales dirigidos por el estado dominan una clase y cada estado tiene transiciones y comportamiento distintos.

5.21 Strategy (de comportamiento)

Encapsular algoritmos intercambiables detrás de una abstracción común.

Aplicaciones típicas: Envío, precios, impuestos, enrutamiento, autenticación, ordenación, reglas de pago.

Señal de diseño

Un patrón de trabajo pesado. Úsalo cuando las alternativas resuelven la misma tarea y el cliente no debe ser dueño de su lógica condicional.

5.22 Template Method (de comportamiento)

Definir el esqueleto de un algoritmo en una clase base permitiendo que las subclases personalicen pasos seleccionados.

Aplicaciones típicas: Canalizaciones de importación, puntos de extensión de frameworks, procesamiento por lotes, flujos de análisis.

Señal de diseño

Útil cuando el orden del algoritmo es estable pero algunos pasos varían. Prefiere la composición cuando la herencia crearía un acoplamiento rígido.

5.23 Visitor (de comportamiento)

Añadir operaciones a una estructura de objetos estable sin cambiar repetidamente las clases de los elementos.

Aplicaciones típicas: AST de compiladores, árboles de documentos, análisis estático, recorridos de modelos complejos.

Señal de diseño

Potente cuando los tipos de elemento son estables y las operaciones cambian con frecuencia; incómodo cuando se añaden nuevos tipos de elemento a menudo.

6. Los patrones que más vas a encontrar en Java

No existe un ranking universal de frecuencia de patrones. El uso depende del dominio, la arquitectura, el framework y el estilo del equipo. Sin embargo, en Java empresarial y en sistemas de backend aparece un grupo recurrente, de forma explícita o encarnado por los frameworks: Strategy, Factory Method, Builder, Adapter, Observer, Decorator, Facade, Singleton, Template Method y Command.

Los diez patrones más frecuentes en Java empresarial, la señal que los delata y la ganancia que aportan.
PatrónSeñal típica en el códigoPor qué ayuda
StrategyVarios algoritmos sirven al mismo propósito y una cadena de switch/if no deja de crecer.Mueve la variación detrás de una abstracción estable y mejora las pruebas independientes.
Factory MethodEl código de negocio elige directamente entre muchos constructores concretos.Separa las decisiones de creación del uso.
BuilderLos constructores largos contienen muchos null, booleanos o argumentos opcionales.Hace legible la construcción y centraliza la validación.
AdapterEl código de dominio importa tipos y reglas de conversión propios del proveedor.Crea una frontera anticorrupción alrededor de las dependencias externas.
ObserverUn evento provoca varias reacciones independientes.Permite nuevas escuchas sin cambiar el emisor.
DecoratorMuchas combinaciones de comportamiento opcional crean una explosión de subclases.Compone el comportamiento dinámicamente.
FacadeUn controlador o cliente debe coordinar demasiados servicios del subsistema.Crea un punto de entrada simple para un caso de uso.
SingletonExactamente una instancia es un requisito real del dominio o de la ejecución.Controla la unicidad, pero no debe usarse como comodidad de acceso global.
Template MethodVarios flujos comparten la misma secuencia pero difieren en algunos pasos.Centraliza el algoritmo invariable y expone puntos de extensión.
CommandLas acciones deben encolarse, registrarse, programarse, reintentarse o deshacerse.Convierte las operaciones en objetos de primera clase.

7. Análisis en profundidad con ejemplos en Java

Las siguientes secciones siguen un modelo de aprendizaje repetible: primero se ve el problema de diseño, después el patrón y después la ganancia en el código. Esto evita que los nombres de los patrones se desliguen de las presiones que los hacen valiosos.

7.1 Patrón Strategy en Java - sustituye algoritmos condicionales por comportamiento intercambiable

Supón que un sistema de comercio electrónico calcula el envío. La primera versión admite SEDEX, entrega estándar y recogida en tienda. Un solo método con ramas if/else es comprensible. Pero después llegan nuevos transportistas, la entrega exprés, la entrega el mismo día, el envío internacional y el envío promocional. La clase de cálculo se convierte en el lugar donde chocan todas las reglas de envío.

public interface ShippingStrategy {
    double calculate(double weight);
}

public final class ExpressShipping implements ShippingStrategy {
    @Override
    public double calculate(double weight) {
        return weight * 15.0;
    }
}

public final class ShippingCalculator {
    private final ShippingStrategy strategy;

    public ShippingCalculator(ShippingStrategy strategy) {
        this.strategy = strategy;
    }

    public double calculate(double weight) {
        return strategy.calculate(weight);
    }
}

Ahora la calculadora es dueña del flujo “calcular el envío”, mientras que cada Strategy es dueño de un algoritmo. Se puede añadir una nueva política de envío sin editar la calculadora. Cada política puede probarse de forma aislada y la selección en tiempo de ejecución se vuelve directa.

Un switch creciente a la izquierda sustituido a la derecha por un cliente que depende de una interfaz de estrategia de envío con tres implementaciones independientes
Figura 3: Strategy saca la variación del contexto - el switch deja de crecer y cada algoritmo se vuelve comprobable de forma independiente.

En Java 8+, Strategy puede ser ligero cuando la abstracción es una interfaz funcional: ShippingStrategy free = weight -> 0.0;. La cuestión no es el número de clases; la cuestión es sacar la variación algorítmica del contexto.

Ganancia en el código

Menos condicionales, menor acoplamiento, algoritmos aislados, pruebas más simples y una alineación más fuerte con el Principio Abierto/Cerrado.

7.2 Factory Method en Java - separa la creación del uso

La lógica de creación se convierte en un problema de diseño cuando los clientes deciden repetidamente qué implementación concreta instanciar. Si el código de notificación está salpicado de new EmailNotifier(), new SmsNotifier() y new PushNotifier(), cambiar las reglas de construcción exige tocar código de negocio que solo debería ocuparse de enviar una notificación.

public interface Notifier {
    void send(String message);
}

public abstract class NotificationService {
    protected abstract Notifier createNotifier();

    public void notify(String message) {
        Notifier notifier = createNotifier();
        notifier.send(message);
    }
}

public final class EmailNotificationService extends NotificationService {
    @Override
    protected Notifier createNotifier() {
        return new EmailNotifier();
    }
}

El Factory Method clásico coloca la operación de creación en una abstracción creadora y deja que los creadores concretos determinen el producto. A un método estático centralizado con un switch se le suele llamar Simple Factory. Puede ser perfectamente útil, pero no es uno de los 23 patrones formales del GoF.

Ganancia en el código

La lógica de negocio depende de abstracciones de producto, la creación queda localizada o extensible, los constructores concretos dejan de propagarse por la base de código y las pruebas pueden sustituir las rutas de creación con más facilidad.

7.3 Patrón Builder en Java - haz que la construcción compleja se lea como una historia

Los constructores largos son peligrosos no porque los constructores sean malos por naturaleza, sino porque los argumentos posicionales dejan de comunicar significado. Considera new User("Ana", "ana@example.com", null, "London", true, false, null). Un lector debe inspeccionar la firma del constructor para saber qué significan true y false, cuál null es un teléfono y qué campos son opcionales.

User user = User.builder()
    .name("Ana")
    .email("ana@example.com")
    .city("London")
    .active(true)
    .build();

Un Builder también puede imponer invariantes en el momento de construir. El método build() puede rechazar un campo obligatorio ausente, normalizar datos o elegir valores por defecto. Esto convierte la construcción en una frontera controlada en lugar de una secuencia pasiva de asignaciones.

Ganancia en el código

Construcción legible, menos errores de orden de parámetros, soporte natural para valores opcionales, validación centralizada y menos presión para crear múltiples constructores telescópicos.

7.4 Patrón Adapter en Java - protege el dominio de las API externas

Una biblioteca externa de pagos puede aceptar importes en céntimos, devolver códigos de estado como texto, usar excepciones propias del proveedor y exponer terminología que no pertenece a tu dominio. Si esos detalles se extienden por servicios y controladores, el proveedor ha reescrito, en la práctica, el lenguaje de tu aplicación.

public interface PaymentGateway {
    boolean pay(double amount);
}

public final class LegacyPaymentAdapter implements PaymentGateway {
    private final LegacyPaymentClient client;

    public LegacyPaymentAdapter(LegacyPaymentClient client) {
        this.client = client;
    }

    @Override
    public boolean pay(double amount) {
        long cents = Math.round(amount * 100);
        String response = client.executePayment(cents);
        return "OK".equals(response);
    }
}

El resto de la aplicación habla PaymentGateway, no LegacyPaymentClient. Si se sustituye al proveedor, la lógica de conversión cambia en la frontera en lugar de en todas partes. Esto es más que compatibilidad de interfaces: es aislamiento arquitectónico.

Clases de dominio que dependen de una interfaz PaymentGateway mientras un adaptador traduce céntimos, códigos de estado como texto y excepciones del proveedor en la frontera
Figura 4: el Adapter es una frontera anticorrupción - los formatos del proveedor se detienen en el borde y el dominio conserva su propio lenguaje.

Ganancia en el código

Los formatos y tipos externos se quedan en el borde, el dominio depende de su propia abstracción, sustituir al proveedor es más barato y las pruebas pueden usar un PaymentGateway falso.

7.5 Patrón Observer en Java - desacopla un evento de sus reacciones

Cuando se paga un pedido, el sistema puede enviar un correo, actualizar el inventario, emitir una factura, actualizar la analítica y notificar a los socios. Si el servicio de pago llama directamente a cada reacción, conoce a todos los consumidores y se convierte en un centro de coordinación que debe cambiar cada vez que se añade una nueva reacción.

public interface OrderPaidObserver {
    void onPaid(Order order);
}

public final class OrderService {
    private final List<OrderPaidObserver> observers = new ArrayList<>();

    public void addObserver(OrderPaidObserver observer) {
        observers.add(observer);
    }

    public void pay(Order order) {
        order.markAsPaid();
        observers.forEach(o -> o.onPaid(order));
    }
}

El patrón GoF en memoria y la publicación/suscripción distribuida no son idénticos: los brokers introducen durabilidad, semántica de entrega, ordenación, reintentos y consistencia. Aun así, la intuición de diseño está relacionada: el productor no debería tener que conocer cada reacción independiente.

Ganancia en el código

Se pueden introducir nuevas escuchas sin modificar el emisor, las responsabilidades quedan separadas y el diseño se convierte en un paso natural hacia el pensamiento orientado a eventos.

7.6 Patrón Decorator en Java - compón comportamiento opcional sin explosión de subclases

Supón que una notificación puede registrarse, cifrarse, medirse, reintentarse y auditarse. Crear una subclase para cada combinación produce rápidamente EmailWithLog, EmailWithLogAndEncryption, SmsWithAuditAndMetrics, etc. El Decorator usa composición en su lugar: cada envoltorio implementa la misma abstracción y delega en otra instancia.

Notifier notifier =
    new LoggingNotifier(
        new EncryptingNotifier(
            new EmailNotifier()));

notifier.send("Order approved");

La E/S de Java es el ejemplo clásico y familiar: un BufferedInputStream envuelve a otro InputStream y conserva la misma abstracción general. La misma idea aparece en middleware, filtros de seguridad, clientes HTTP, observabilidad y preocupaciones transversales.

Ganancia en el código

El comportamiento se vuelve componible de forma independiente, se evita la explosión de subclases y el orden de los envoltorios puede configurarse en tiempo de ejecución.

7.7 Patrón Facade en Java - convierte un subsistema en un punto de entrada claro de caso de uso

Un endpoint de checkout no debería tener que entender la validación de inventario, el cálculo del envío, la captura del pago, la emisión de la factura, la persistencia y la notificación al cliente. Cuando un controlador coordina todos esos detalles, el código de presentación queda acoplado a la topología interna del subsistema.

public final class CheckoutFacade {
    public CheckoutResult checkout(Order order) {
        inventory.validate(order);
        shipping.calculate(order);
        payment.charge(order);
        invoice.issue(order);
        repository.save(order);
        return CheckoutResult.success(order.getId());
    }
}

// Controller
return checkoutFacade.checkout(order);

Un Facade crea una interfaz cómoda de nivel superior. No significa que todo servicio interno deba volverse privado o inaccesible. El objetivo es dar a los casos de uso comunes un punto de entrada estable e impedir que quienes llaman dependan de detalles innecesarios.

Ganancia en el código

Controladores más pequeños, orquestación centralizada, menor acoplamiento entre cliente y subsistema y más libertad para evolucionar los componentes internos.

7.8 Patrón Singleton en Java - entiende el requisito antes que la comodidad

El Singleton es famoso porque su implementación es fácil de reconocer. Esa fama puede resultar engañosa. La pregunta importante no es “¿cómo escribo getInstance()?”, sino “¿la unicidad forma parte realmente del modelo?”. Si la motivación real es simplemente “quiero acceder a este objeto desde cualquier sitio”, el resultado suele ser un acoplamiento global oculto.

public enum GlobalConfiguration {
    INSTANCE;

    public void reload() {
        // ...
    }
}

Un enum puede proporcionar un singleton robusto a nivel de JVM en ciertos casos, pero la inyección de dependencias moderna suele ofrecer un diseño mejor. Un bean de Spring puede tener ámbito singleton y aun así inyectarse explícitamente en los consumidores. Eso hace visibles las dependencias en los constructores y facilita su sustitución en las pruebas.

Ganancia en el código - solo cuando se justifica

Unicidad controlada y ciclo de vida consistente. Costo cuando se usa mal: estado global, dependencias ocultas, fricción en las pruebas, acoplamiento de inicialización y complejidad de concurrencia.

7.9 Template Method en Java - mantén estable el algoritmo y varía pasos seleccionados

Los importadores de CSV y JSON pueden compartir una secuencia general: abrir, validar, leer, transformar, guardar, finalizar. Si cada importador reimplementa el flujo completo, las partes estables se duplican. El Template Method coloca el esqueleto del algoritmo en una clase base y delega pasos seleccionados en las subclases.

public abstract class Importer {
    public final void importData() {
        open();
        validate();
        Object data = read();
        Object transformed = transform(data);
        save(transformed);
        finish();
    }

    protected abstract void validate();
    protected abstract Object read();
    protected abstract Object transform(Object data);

    protected void open() {}
    protected void save(Object data) {}
    protected void finish() {}
}

El compromiso es la herencia. Si las variaciones se vuelven numerosas o necesitan combinarse de forma independiente, la composición puede ser más flexible. El Template Method es más fuerte cuando el propio flujo es estable y los puntos de extensión están limitados a propósito.

Ganancia en el código

El orden común queda centralizado, la duplicación disminuye y los puntos de extensión se vuelven explícitos.

7.10 Patrón Command en Java - convierte las acciones en objetos

Un botón, una cola, un planificador o un motor de flujos de trabajo no deberían necesitar conocer los detalles de implementación de cada acción que pueden disparar. El Command envuelve una operación en un objeto que expone un contrato de ejecución común.

public interface Command {
    void execute();
}
public final class SaveDocumentCommand implements Command {
    private final DocumentFile document;

    public SaveDocumentCommand(DocumentFile document) {
        this.document = document;
    }

    @Override
    public void execute() {
        document.save();
    }
}

Queue<Command> jobs = new ArrayDeque<>();
jobs.add(new SaveDocumentCommand(document));

Una vez que la acción es un objeto, puede almacenarse, registrarse, programarse, reintentarse, agruparse o emparejarse con una operación inversa para deshacer/rehacer. Ese es el valor más profundo del patrón: convierte una operación de sintaxis de flujo de control en dato manipulable.

Ganancia en el código

El disparo queda desacoplado de la ejecución, las acciones pueden encolarse o auditarse y los flujos ganan una programación flexible y capacidad de reintento.

8. Cómo reconocer patrones en código existente

Reconocer patrones es más útil que memorizarlos. La forma más rápida de aprender es mapear los malos olores del código o las presiones de diseño a patrones candidatos y luego preguntarse si los compromisos están justificados.

Preguntas que revelan una fuerza de diseño y el patrón que normalmente conviene considerar.
Pregunta a formularPatrón candidato
¿Tengo varios algoritmos para la misma tarea?Strategy
¿El código de negocio está lleno de expresiones new concretas usadas para seleccionar implementaciones?Factory Method u otro patrón creacional
¿Este constructor tiene demasiados argumentos o parámetros opcionales?Builder
¿El dominio habla el vocabulario de una API de terceros?Adapter
¿Muchos componentes reaccionan de forma independiente a un evento?Observer
¿Necesito combinaciones de comportamiento sin una subclase por combinación?Decorator
¿Debe un llamador conocer muchos servicios del subsistema para completar una operación de negocio?Facade
¿Las ramas de if/switch dependen sobre todo del estado actual?State
¿Una solicitud pasa por varios manejadores opcionales?Chain of Responsibility
¿Necesito controlar acceso, ciclo de vida, pereza o invocación remota detrás de la misma interfaz?Proxy

Observa el lenguaje de las preguntas: varios algoritmos, vocabulario externo, muchas escuchas, combinaciones de comportamiento, estado actual. Esas son las fuerzas. El nombre del patrón es solo una etiqueta compacta para una respuesta probada a esas fuerzas.

9. Patrones GoF y principios SOLID

SOLID y GoF están relacionados pero no son equivalentes. SOLID aporta principios que guían la dirección del diseño orientado a objetos. GoF aporta estructuras con nombre que pueden ayudar a realizar algunos de esos principios en situaciones recurrentes. Un patrón no debe justificarse solo diciendo “es SOLID”, y un diseño SOLID no exige un patrón GoF.

9.1 Strategy y el Principio Abierto/Cerrado

Un condicional grande suele significar que cada algoritmo nuevo modifica una clase existente. Con Strategy, un algoritmo nuevo puede introducirse como una nueva implementación de una abstracción existente. El contexto puede permanecer cerrado a la modificación mientras el conjunto de estrategias permanece abierto a la extensión. Esta es una forma práctica del Principio Abierto/Cerrado.

9.2 Adapter e Inversión de Dependencias

Cuando el dominio depende de PaymentGateway en lugar de VendorXClient, la política de alto nivel queda protegida de un detalle de bajo nivel. El Adapter traduce entre ambos. Esto está muy alineado con el Principio de Inversión de Dependencias: la política estable depende de una abstracción que pertenece a la aplicación, no de la interfaz concreta del proveedor.

9.3 Facade y fronteras de responsabilidad

Un Facade puede dar a un caso de uso una frontera clara de orquestación y evitar que los controladores coordinen detalles que no deberían comprender. Aun así, un Facade enorme que conoce todos los subsistemas puede convertirse en un God Object. Los patrones no eliminan la necesidad de disciplina en las responsabilidades.

9.4 Segregación de Interfaces y las interfaces de los patrones

Los patrones suelen introducir interfaces, pero “más interfaces” no es automáticamente un mejor diseño. La interfaz debe expresar un papel significativo. Una estrategia con una operación enfocada suele ser natural; una interfaz que solo refleja una clase concreta enorme puede aportar poco desacoplamiento. SOLID ayuda a evaluar la calidad de las abstracciones en las que se apoyan los patrones.

10. Dónde aparecen los patrones en el ecosistema Java

10.1 Spring

Spring usa muchas ideas que se discuten con naturalidad usando el vocabulario de los patrones. La inyección de dependencias y el contenedor IoC se relacionan con la creación de objetos y las fábricas; los eventos de aplicación recuerdan a Observer; AOP suele apoyarse en proxies; las interfaces al estilo strategy aparecen por todo el framework; y las clases template centralizan históricamente flujos estables mientras exponen callbacks. Las tripas del framework no siempre coinciden exactamente con las estructuras GoF de manual, pero el vocabulario facilita razonar sobre la arquitectura.

10.2 Hibernate y JPA

Las entidades con carga diferida pueden representarse mediante proxies. EntityManagerFactory encarna claramente un papel de creación. Los frameworks de persistencia también emplean patrones empresariales más allá del GoF - como Unit of Work e Identity Map -, lo que demuestra que el GoF es una base, no el universo completo de los patrones de software.

10.3 E/S de Java

La jerarquía de InputStream es una demostración canónica de composición al estilo Decorator. Un BufferedInputStream envuelve a otro InputStream y añade búfer sin cambiar el contrato conceptual del cliente. Una vez que reconoces esta estructura, la construcción anidada de flujos deja de parecer arbitraria.

10.4 Collections e Iterator

El Iterator está integrado directamente en el ecosistema Java Collections. El for mejorado oculta la mecánica, pero el principio de diseño permanece: los clientes recorren los elementos sin depender de la representación de la colección.

10.5 Inyección de dependencias y la conversación moderna sobre Singleton

El ámbito singleton gestionado por el framework no debe confundirse con el acceso estático global. Un contenedor de inyección de dependencias puede gestionar una instancia preservando relaciones de dependencia explícitas. Esa distinción es central para usar ámbitos de ciclo de vida sin reproducir los problemas de pruebas y acoplamiento de las implementaciones manuales de Singleton.

11. Comparaciones de patrones que evitan errores comunes

Pares de patrones que se confunden con frecuencia y la diferencia que los separa.
PatronesDiferencia clave
Strategy vs. StateAmbos delegan comportamiento. Strategy representa un algoritmo elegido; State representa un comportamiento que cambia a medida que el objeto recorre un ciclo de vida, a menudo con transiciones dirigidas por el estado.
Decorator vs. ProxyAmbos envuelven un objeto compatible. El Decorator compone principalmente responsabilidades; el Proxy controla principalmente el acceso, el ciclo de vida, la pereza, la seguridad o la comunicación remota.
Adapter vs. FacadeEl Adapter cambia una interfaz para que los componentes puedan cooperar. El Facade simplifica un subsistema ofreciendo un punto de entrada de nivel superior.
Factory Method vs. Abstract FactoryEl Factory Method se centra en una operación de creación que las subclases o creadores pueden variar. El Abstract Factory crea familias coordinadas de productos relacionados.
Factory Method vs. Simple FactoryUn Simple Factory centraliza una decisión de construcción, a menudo con un switch. Útil, pero no es uno de los 23 patrones formales del GoF.
Builder vs. FactoryUna Factory decide qué objeto o implementación crear. Un Builder se centra en cómo se ensambla paso a paso un objeto complejo.
Bridge vs. StrategyEl Bridge separa dos dimensiones estructurales que varían de forma independiente. El Strategy intercambia algoritmos que cumplen el mismo papel.
Template Method vs. StrategyEl Template Method usa herencia para variar pasos dentro de un algoritmo fijo. El Strategy usa composición para sustituir un algoritmo o una política.
Observer vs. MediatorEl Observer difunde el cambio a las escuchas interesadas. El Mediator centraliza la colaboración entre pares para reducir las dependencias cruzadas directas.
Chain of Responsibility vs. DecoratorUna cadena enruta una solicitud por manejadores que pueden continuar o detenerse. Un decorador envuelve un componente para añadir comportamiento conservando su interfaz.

12. Aplicación práctica: refactorizar un checkout de comercio electrónico

Un ejemplo pequeño puede mostrar cómo los patrones se materializan juntos sin convertir el diseño en un museo de patrones. Considera un servicio de checkout que hoy ejecuta seis responsabilidades en un solo método: elegir un cálculo de envío con un switch, llamar directamente al SDK de pago de un proveedor, aplicar reglas promocionales con otro switch, escribir mensajes de auditoría, emitir una factura y notificar a varios componentes posteriores.

12.1 Paso 1 - identifica los ejes de cambio

  • Los algoritmos de envío crecerán de forma independiente.
  • Los algoritmos de promoción cambian con frecuencia.
  • El proveedor de pago puede ser sustituido.
  • La auditoría y las métricas son transversales y opcionales.
  • El endpoint de checkout debe exponer una operación de negocio.
  • Varios consumidores independientes reaccionan tras el pago.

Cada afirmación describe una fuerza. Solo después de identificar esas fuerzas deberían entrar los patrones en la conversación.

12.2 Paso 2 - asigna fuerzas a estructuras candidatas

Cada fuerza del checkout y la estructura que la responde.
FuerzaCandidato
Algoritmos intercambiables de envío y promociónStrategy
SDK de pago externo con vocabulario incompatibleAdapter
Auditoría/métricas opcionales alrededor de una pasarela o servicioDecorator
Un punto de entrada claro para el checkoutFacade
Reacciones independientes tras el pago correctoObserver / estilo evento de dominio
Seleccionar una estrategia concreta a partir de la configuraciónFactory Method o una pequeña fábrica/registro
Método de checkout con seis responsabilidades mezcladas a la izquierda y el mismo flujo a la derecha dividido en strategy, adapter, decorator, facade y escuchas
Figura 5: cada fuerza del checkout obtiene su propia estructura - el flujo sigue igual, pero cada eje de cambio se traslada a su propia frontera.

12.3 Paso 3 - mantén pequeños los contratos orientados al dominio

public interface ShippingPolicy {
    Money calculate(Order order);
}

public interface PaymentGateway {
    PaymentResult charge(Order order);
}

public interface OrderPaidListener {
    void onOrderPaid(Order order);
}

Los contratos expresan papeles de negocio. No exponen céntimos, cadenas de estado del proveedor, objetos de respuesta HTTP ni detalles específicos del framework. Ese es el beneficio arquitectónico del diseño: los conceptos estables quedan dentro; los detalles volátiles se quedan en los bordes.

12.4 Paso 4 - orquesta, no microgestiones

public CheckoutResult checkout(Order order) {
    Money shipping = shippingPolicy.calculate(order);
    order.applyShipping(shipping);

    PaymentResult paymentResult = paymentGateway.charge(order);
    if (!paymentResult.approved()) {
        return CheckoutResult.rejected(paymentResult.reason());
    }

    order.markAsPaid();
    repository.save(order);
    listeners.forEach(l -> l.onOrderPaid(order));
    return CheckoutResult.success(order.id());
}

Esto sigue siendo código corriente. Los patrones no han eliminado condicionales, métodos ni datos. Han trasladado la variación y los detalles de integración a fronteras donde esas preocupaciones pueden cambiar de forma independiente. Esa es la definición práctica de un diseño útil.

12.5 Paso 5 - prueba las costuras

Como el checkout depende de abstracciones pequeñas, las pruebas unitarias pueden inyectar un ShippingPolicy falso y un PaymentGateway falso. Las pruebas de integración pueden centrarse específicamente en el Adapter. Las pruebas de escuchas pueden verificar reacciones independientes. La arquitectura de pruebas resultante refleja la arquitectura de responsabilidades.

13. Sobreingeniería: cuándo no usar un patrón

Una de las lecciones más importantes del manuscrito original es la advertencia contra “usar un patrón por vanidad”. Una función de dos líneas que duplica un número no necesita DoubleStrategy, DoubleFactory, DoubleFacade, DoubleBuilder y cinco interfaces. El conocimiento de patrones debe reducir la complejidad accidental, no generarla.

Vale la pena considerar un patrón cuando resuelve un problema real y recurrente, reduce un acoplamiento dañino, crea un punto de extensión valioso, aclara la intención o protege un área estable de detalles volátiles. Resulta sospechoso cuando solo aumenta el número de clases, añade indirección sin una presión de cambio, oculta una lógica que ya era obvia o se introduce únicamente porque el patrón está de moda.

Regla práctica

Prefiere el diseño más simple que siga siendo claro ante los cambios que puedas prever razonablemente. Refactoriza hacia un patrón cuando la presión se vuelva visible; no construyas de antemano toda abstracción posible para cambios que quizá nunca ocurran.

Por eso una sentencia if puede ser la solución correcta hoy y Strategy la solución correcta seis meses después. El contexto decide. La calidad de un diseño no se mide por el número de patrones con nombre que contiene.

14. Guía de decisión y matriz de selección de patrones

Los 23 patrones con la familia a la que pertenecen y la situación que hace que cada uno merezca consideración.
PatrónFamiliaConsidéralo cuando...
Abstract FactoryCreacionalLas familias de productos deben variar juntas
BuilderCreacionalLa construcción tiene muchos parámetros o pasos ordenados
Factory MethodCreacionalLa creación concreta debe desacoplarse o ser extensible
PrototypeCreacionalCopiar un modelo configurado es mejor que reconstruirlo
SingletonCreacionalLa unicidad es genuinamente necesaria
AdapterEstructuralLa interfaz externa no coincide con la de la aplicación
BridgeEstructuralDos dimensiones varían de forma independiente
CompositeEstructuralLos objetos hoja y grupo forman un árbol
DecoratorEstructuralLos comportamientos deben combinarse dinámicamente
FacadeEstructuralUn subsistema es demasiado complejo para quienes llaman
FlyweightEstructuralPoblaciones enormes de objetos duplican estado intrínseco
ProxyEstructuralEl acceso o el ciclo de vida deben controlarse tras la misma interfaz
Chain of ResponsibilityDe comportamientoUna solicitud pasa por manejadores ordenados
CommandDe comportamientoUna acción debe volverse almacenable/manipulable
InterpreterDe comportamientoUna gramática pequeña necesita evaluarse
IteratorDe comportamientoEl recorrido debe ocultar la representación de la colección
MediatorDe comportamientoLos pares tienen demasiadas dependencias cruzadas
MementoDe comportamientoEl estado debe restaurarse después
ObserverDe comportamientoMuchos consumidores independientes reaccionan a un evento
StateDe comportamientoEl comportamiento depende mucho del estado del ciclo de vida
StrategyDe comportamientoUn algoritmo debe ser intercambiable
Template MethodDe comportamientoUn flujo es estable pero varían pasos seleccionados
VisitorDe comportamientoLas operaciones cambian más a menudo que los tipos de elemento

14.1 Un mapa mental que empieza por el problema

Mapa de decisión que enlaza preguntas de diseño como intercambiar un algoritmo o envolver la API de un proveedor con el patrón que responde a cada una
Figura 6: empieza por la pregunta, no por el catálogo - cada presión de diseño apunta a la estructura que la alivia.
¿Necesitas intercambiar un algoritmo?                       -> Strategy
¿Necesitas controlar/extender la creación?                  -> Factory Method
¿Construcción de objeto compleja?                           -> Builder
¿Interfaz de terceros incompatible?                         -> Adapter
¿Necesitas comportamiento opcional componible?              -> Decorator
¿Subsistema demasiado complejo para quien llama?            -> Facade
¿Muchas escuchas reaccionan a un evento?                    -> Observer
¿La acción debe encolarse/almacenarse/reintentarse?         -> Command
¿El comportamiento cambia con el estado del ciclo de vida?  -> State
¿La solicitud pasa por etapas de procesamiento?    -> Chain of Responsibility
¿Dos dimensiones varían de forma independiente?    -> Bridge
¿Árbol de hojas y grupos?                          -> Composite
¿Necesitas un envoltorio de acceso/pereza/remoto?  -> Proxy

14.2 Autoevaluación del lector

  • ¿Puedes explicar por qué un patrón no es una receta de código?
  • ¿Puedes enumerar los 5 patrones creacionales, los 7 estructurales y los 11 de comportamiento?
  • ¿Puedes distinguir Strategy de State?
  • ¿Puedes distinguir Decorator de Proxy?
  • ¿Puedes explicar por qué Simple Factory no es uno de los 23 patrones formales del GoF?
  • ¿Puedes describir cómo Adapter protege el dominio de las API de proveedores?
  • ¿Puedes explicar por qué Singleton merece cautela en aplicaciones con inyección de dependencias?
  • ¿Puedes identificar cuándo un if/switch sigue siendo más simple y mejor que introducir un patrón?

15. Conclusión

Los 23 patrones de diseño GoF son más valiosos cuando dejan de parecer 23 recetas y empiezan a parecer 23 lentes para examinar problemas de diseño recurrentes. Los patrones creacionales preguntan cómo se pueden crear objetos sin atar el sistema innecesariamente a detalles concretos de construcción. Los patrones estructurales preguntan cómo se pueden combinar objetos y clases sin volver rígido el diseño. Los patrones de comportamiento preguntan cómo deben distribuirse los algoritmos, el estado, la comunicación y la responsabilidad.

En el Java del día a día, Strategy, Factory Method, Builder, Adapter, Observer, Decorator, Facade, Singleton, Template Method y Command son especialmente útiles de reconocer. Pero el objetivo nunca es “tener más interfaces” ni “usar un patrón famoso”. Strategy es valioso porque los algoritmos pueden cambiar sin desmontar su contexto. Adapter es valioso porque las dependencias externas dejan de dictar el lenguaje del dominio. Builder es valioso porque la construcción compleja se vuelve legible y segura. Facade es valioso porque un subsistema complejo puede exponer un punto de entrada de negocio claro. Command es valioso porque las acciones se convierten en objetos que pueden programarse, reintentarse, registrarse o deshacerse.

La lección más profunda es el criterio. Un buen ingeniero no empieza con “¿qué patrón puedo usar?”. El ingeniero empieza con “¿qué está cambiando? ¿Qué está acoplado? ¿Qué se repite? ¿Qué debería permanecer estable? ¿Cuál es el diseño más simple capaz de absorber el cambio probable?”. A veces la respuesta es un patrón. A veces la respuesta es un método directo y un if claro.

Mi valoración es que el GoF sigue siendo una de las bases más útiles para la enseñanza del diseño de software precisamente porque enseña un vocabulario transferible. Los frameworks modernos, las construcciones funcionales, la inyección de dependencias, los sistemas de eventos y las arquitecturas de nube pueden cambiar la forma de la implementación, pero las fuerzas fundamentales del diseño permanecen. Cuando puedes mirar un condicional que crece y preguntar “¿Strategy o State?”, mirar un SDK de proveedor filtrándose al dominio y preguntar “¿Adapter?”, o mirar un controlador que coordina ocho servicios y preguntar “¿Facade?”, el tema ha pasado de la memorización al diseño de software.

Reflexión final

La mejor prueba de que entiendes los patrones de diseño no es poder nombrar los 23. Es poder explicar por qué un patrón abarata un cambio concreto - y por qué, en otro contexto, ningún patrón es el mejor diseño.

Referencias y lecturas complementarias

  1. Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
  2. Alexander, Christopher; Ishikawa, Sara; Silverstein, Murray et al. A Pattern Language: Towns, Buildings, Construction. Oxford University Press, 1977.
  3. Fowler, Martin. Refactoring: Improving the Design of Existing Code. Addison-Wesley.
  4. Martin, Robert C. Clean Architecture y textos sobre los principios SOLID.
  5. El manuscrito GoF/Java proporcionado, usado como base editorial principal de este artículo ampliado.