Los patrones de diseño describen soluciones reutilizables a problemas recurrentes dentro del desarrollo orientado a objetos. No se trata de recetas rígidas sino de guías que condensan experiencias contrastadas, de forma que diferentes equipos puedan comunicarse usando un vocabulario común y evitar errores ya conocidos.
El catálogo más influyente nació con el libro Design Patterns: Elements of Reusable Object-Oriented Software, publicado en 1994 por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides, grupo reconocido como el Gang of Four (GoF - Banda de los cuatro). Desde entonces, los patrones se convirtieron en un puente entre el diseño conceptual y el código concreto.
La idea de capturar soluciones arquitectónicas repetibles proviene de la obra del arquitecto Christopher Alexander, quien documentó patrones urbanos y edilicios reutilizables. Gamma y su equipo adoptaron esta noción en el terreno del software para responder a los retos de la programación orientada a objetos de los años noventa: bases de código extensas, interfaces gráficas cada vez más complejas y la necesidad de colaborar en equipos distribuidos.
Comprender el trasfondo histórico ayuda a evitar extremismos. Los patrones no son un fin en sí mismos; son un lenguaje para describir soluciones maduras que demuestran su utilidad cuando el problema se repite en distintos proyectos.
Cada patrón del catálogo GoF sigue una estructura que facilita su lectura y puesta en práctica:
Dominar esta estructura nos permite identificar patrones en código existente y documentar soluciones propias que puedan enseñarse a otros equipos.
La lectura de un patrón es más eficaz si se parte de un problema concreto. Comience analizando el contexto y rodéese de ejemplos antes de estudiar la solución abstracta. Luego, valide si las consecuencias y los compromisos descritos encajan con la realidad de su proyecto. Finalmente, plasme su implementación particular cuidando el lenguaje compartido: use diagramas simples, referencias a clases reales y mantenenga la misma terminología que el patrón para que otras personas sigan el hilo.
Cuando documente un patrón aplicado en su organización, capture el antes y el después, explicite las decisiones que quedaron descartadas y describa cómo probar la solución. Esto facilita que nuevas personas adopten el patrón con criterio y evita que el conocimiento se pierda con el tiempo.
Aplicar patrones sin criterio puede introducir complejidad innecesaria. Es común incurrir en la llamada “fiebre del patrón”, donde se fuerza el uso de estructuras elaboradas en problemas simples. También se confunde la etiqueta del patrón con una implementación particular, perdiendo flexibilidad ante nuevas necesidades. La clave es comprender el problema y evaluar si el patrón aporta más beneficios que costos en ese contexto específico.
Recuerde que un patrón no reemplaza la validación mediante pruebas automatizadas ni el análisis de rendimiento. Constituye una guía, no una garantía absoluta de calidad.
El siguiente ejemplo ilustra cómo un patrón puede convertir condicionales rígidos en una solución flexible. Partimos de un servicio que envía notificaciones según el canal elegido por la persona usuaria:
class NotificationService {
void notify(String channel, String message) {
if ("EMAIL".equals(channel)) {
// Código para enviar correo electrónico
} else if ("SMS".equals(channel)) {
// Código para enviar SMS
} else if ("PUSH".equals(channel)) {
// Código para enviar notificación push
} else {
throw new IllegalArgumentException("Canal no soportado: " + channel);
}
}
}
Esta implementación crece en complejidad a medida que aparecen nuevos canales. Al aplicar el patrón Strategy y apoyarnos en el lenguaje Java, distribuimos la lógica en estrategias independientes y logramos un código abierto a la extensión:
interface NotificationStrategy {
void send(String message);
}
class EmailNotification implements NotificationStrategy {
public void send(String message) {
// Integración con SMTP
}
}
class SmsNotification implements NotificationStrategy {
public void send(String message) {
// Integración con proveedor SMS
}
}
class PushNotification implements NotificationStrategy {
public void send(String message) {
// Integración con servicio push
}
}
class NotificationService {
private final Map<String, NotificationStrategy> strategies;
NotificationService(Map<String, NotificationStrategy> strategies) {
this.strategies = strategies;
}
void notify(String channel, String message) {
NotificationStrategy strategy = strategies.get(channel);
if (strategy == null) {
throw new IllegalArgumentException("Canal no soportado: " + channel);
}
strategy.send(message);
}
}
El uso de estrategias permite agregar nuevos canales registrando otra implementación, sin modificar el servicio principal. Además, facilita las pruebas unitarias, porque cada estrategia puede validarse de manera aislada. Más adelante veremos cada uno de estos patrones en profundidad.
Conocer la motivación y la anatomía de los patrones prepara el terreno para recorrer el catálogo GoF por familias: patrones creacionales para construir objetos, estructurales para organizarlos y de comportamiento para regular sus interacciones. En los próximos temas exploraremos cada categoría con mayor detalle, analizando cuándo aplicarla y cómo evitar su uso indiscriminado.