Siete patrones estructurales después, toca lo mismo que hicimos al cerrar los creacionales: ponerlos sobre la mesa juntos, que es donde se decide de verdad. Y en esta familia la comparativa es más necesaria que en ninguna otra, porque cuatro de sus miembros —Adapter, Decorator, Facade y Proxy— son mecánicamente casi indistinguibles: un objeto delante de otro, delegando. Elegir mal entre ellos no rompe el programa; rompe algo más caro: la comunicación del diseño, porque el nombre del patrón es una promesa sobre la intención, y una promesa equivocada despista a todo el que venga detrás. Tabla general, careo de envoltorios, guía de decisión, combinaciones y el mapa estructural completo de PideYa: vamos a ello.
Contenido
- Los siete, frente a frente
- Los cuatro envoltorios: misma mecánica, intenciones distintas
- Guía de decisión
- Cómo se combinan entre sí
- El mapa estructural de PideYa
- Errores comunes, ejercicios y conclusión
Los siete, frente a frente
La tabla del panorama de la introducción, ahora con lo aprendido: síntoma que lo dispara y coste que se paga.
| Patrón | Intención en una frase | Síntoma que lo dispara | Coste principal |
|---|---|---|---|
| Adapter | Traducir la interfaz de una pieza intocable a la que el cliente espera | Interfaces incompatibles con código ajeno/legado | Una clase traductora por adaptee; disciplina de frontera estanca |
| Bridge | Separar abstracción e implementación en dos jerarquías conectadas por composición | Subclases con nombre compuesto (ConfirmacionPush): dos ejes fundidos multiplicándose |
Diseño a priori; una indirección; dos jerarquías que mantener |
| Composite | Tratar uniformemente hojas y grupos en árboles parte-todo | El mismo if (esGrupo) repetido en cada operación |
Interfaz común que consensuar; dilema transparencia/seguridad; cuidado con ciclos |
| Decorator | Añadir responsabilidades a un objeto envolviéndolo, combinables en ejecución | Explosión de subclases por combinaciones de extras opcionales | Cebolla opaca; muchas clases pequeñas; el orden de capas importa |
| Facade | Ofrecer una puerta simple ante un subsistema complejo | La misma orquestación copiada en varios clientes | Riesgo de engordar; tentación de meterle negocio |
| Flyweight | Compartir el estado intrínseco de multitudes de objetos | Miles de instancias duplicando datos pesados, memoria medida al límite | Separar intrínseco/extrínseco complica firmas; exige inmutabilidad |
| Proxy | Sustituto con la misma interfaz que controla el acceso al real | Objeto caro, sensible, remoto o repetidamente consultado, con acceso sin control | Indirección invisible; fidelidad al contrato; cachés que invalidar |
Dos ejes transversales para ordenarlos mentalmente:
- ¿Cuántos objetos organiza? Uno frente a otro: Adapter, Decorator, Proxy. Muchos tras uno: Facade (un subsistema), Composite (un árbol), Flyweight (una multitud). Dos jerarquías enteras: Bridge.
- ¿Cambia la interfaz que ve el cliente? La cambia: Adapter. La simplifica: Facade. La conserva exactamente: Decorator, Proxy, Composite (el grupo ofrece la de la hoja). La parte en dos: Bridge. Le da igual: Flyweight (su tema es la memoria, no la interfaz).
Los cuatro envoltorios: misma mecánica, intenciones distintas
El careo estrella. Los cuatro comparten el gesto —recibir llamadas dirigidas a otro y decidir qué hacer con ellas— y se distinguen por lo que prometen:
| Aspecto | Adapter | Decorator | Facade | Proxy |
|---|---|---|---|---|
| Interfaz resultante | Distinta de la del envuelto (la que el cliente esperaba) | La misma del envuelto | Nueva y más simple que las del subsistema | La misma del envuelto |
| Qué promete | Traducción fiel, nada más | El envuelto más responsabilidades visibles | El caso de uso común sin conocer las tripas | El envuelto, con el acceso vigilado |
| ¿Altera el comportamiento observable? | No (solo el idioma) | Sí: añade, y esa es la gracia | No añade reglas: coordina las existentes | Puede negarse, posponer o responder de memoria |
| Envuelve a... | 1 objeto ajeno/legado | 1 objeto propio, a menudo ya envuelto (capas) | N objetos (el subsistema) | 1 objeto propio, normalmente el "de verdad" |
| ¿Quién lo pone ahí? | El integrador, en la frontera | El cliente o quien compone, apilando a la carta | El arquitecto, como puerta del módulo | La infraestructura, sin que el cliente lo sepa |
| En PideYa | AdaptadorPayPal |
ExtraDobleQueso, NotificadorConReintentos |
FachadaCheckout |
ProxyImagenPlato, ProxyProteccionGestionPedidos |
Cuatro preguntas en orden, que resuelven el 90% de las dudas de clasificación:
- ¿La interfaz de fuera es otra que la de dentro? → Adapter (si es más simple y agrupa varios, → Facade).
- Misma interfaz: ¿el envoltorio añade comportamiento visible que el cliente quiere y combina? → Decorator.
- Misma interfaz: ¿el envoltorio decide sobre el acceso (cuándo, quién, cuántas veces, dónde) sin que el cliente participe? → Proxy.
- ¿Sigue la duda (un logging, unas métricas)? Estás en la frontera Decorator/Proxy que ya trabajamos en la lección de Proxy: decide por quién lo compone y para qué — capa opcional apilada por el desarrollador, Decorator; control interpuesto de oficio por la infraestructura, Proxy — y documenta la decisión. La frontera existe; fingir que no, confunde más.
Guía de decisión
El flujo de preguntas del módulo entero. Como la guía creacional: orienta el caso típico, y el filtro previo sigue vigente — ¿de verdad hay síntoma, o estamos decorando por deporte?
flowchart TD
A[Problema al componer<br/>u organizar objetos] --> B{¿Es de MEMORIA?<br/>miles de objetos, datos repetidos,<br/>profiler en rojo}
B -- Sí --> FW[Flyweight<br/>intrínseco compartido + fábrica]
B -- No --> C{¿La estructura del dominio<br/>es un ÁRBOL parte-todo?}
C -- Sí --> CP[Composite<br/>hojas y grupos uniformes]
C -- No --> D{¿Una jerarquía crece por<br/>PRODUCTO de dos ejes<br/>independientes?}
D -- Sí --> BR[Bridge<br/>abstracción × implementación]
D -- No --> E{¿El problema está en<br/>UN objeto al que pongo<br/>algo delante?}
E -- No --> F{¿Muchos clientes repiten la<br/>orquestación de un subsistema?}
F -- Sí --> FC[Facade<br/>puerta de alto nivel]
F -- No --> Z[Quizá no es un problema<br/>estructural: revisa creacionales<br/>o espera al módulo 4]
E -- Sí --> G{¿Su interfaz NO es<br/>la que necesito?}
G -- Sí --> AD[Adapter<br/>traducir en la frontera]
G -- No --> H{¿Quiero AÑADIR comportamiento<br/>combinable y visible?}
H -- Sí --> DC[Decorator<br/>capas apilables]
H -- No --> PX[Proxy<br/>controlar el acceso:<br/>virtual, protección, caché, remoto]
Consejos de uso, como siempre: las respuestas pueden ser varias (un subsistema con fachada puede contener un composite cacheado por un proxy); la guía se aplica por decisión de diseño, no por sistema; y si ninguna rama encaja, la salida "quizá no es estructural" es una respuesta legítima — los problemas de comunicación y reparto de responsabilidades en ejecución pertenecen al módulo que viene.
Cómo se combinan entre sí
Como los creacionales, los estructurales trabajan en cuadrilla. Las combinaciones que ya viste u olfateaste en PideYa:
- Composite + Decorator: comparten interfaz Component por diseño; un decorador puede envolver cualquier nodo del árbol. Los extras (
ExtraDobleQueso) decoran platos que viven en la carta Composite; ambos son, para el cliente,Producto/ComponenteCarta. - Composite + Flyweight: las hojas repetidas de un árbol enorme se comparten como flyweights (el GoF lo señala expresamente); en PideYa, si cada restaurante de una franquicia repite la misma carta base, los
Platocompartidos son candidatos. - Proxy + Composite:
ProxyCacheCatalogodevuelve árboles de carta completos: el proxy controla el acceso; el composite estructura lo accedido. - Facade sobre todos:
FachadaCheckoutcoordina un subsistema donde ya trabajan la Abstract Factory de mercados, el Builder dePedido, adaptadores de pasarelas y notificadores decorados. La fachada no compite con ellos: los esconde. - Adapter dentro de Bridge: un implementor concreto (
CanalWhatsApp) puede ser internamente un adaptador del SDK del proveedor. El puente da la forma; el adaptador, la frontera. - Decorator/Proxy apilados entre sí: logging por fuera de reintentos, protección por dentro de logging... El orden de capas es semántica (qué se registra, qué se reintenta): lo comprobaste en los ejercicios de ambas lecciones.
- Creacionales al servicio de estructurales: las fábricas montan las estructuras — deciden si entregan el objeto real o su proxy, componen la pila estándar de decoradores, eligen el adaptador del mercado. La frase del módulo 2 sigue viva: cada patrón gobierna una capa de la decisión.
El mapa estructural de PideYa
El repaso final del sistema, como cerramos el módulo creacional: qué quedó instalado, dónde y por qué — con la última fila de siempre, la más importante.
| Parte de PideYa | Solución estructural | Por qué esa y no otra |
|---|---|---|
| Cobros con el SDK heredado de PayPal | Adapter (AdaptadorPayPal implements PasarelaPago) |
Interfaz ajena e intocable que debía parecer una pasarela más; toda la aduana en un punto |
| Catálogo del agregador externo | Adapter (AdaptadorAgregador) |
Mismo síntoma, otra frontera: modelo de terceros traducido al nuestro |
| Notificaciones: tipos × canales | Bridge (Notificacion ⇄ CanalEnvio) |
Dos ejes independientes creciendo por producto (9 clases) → crecimiento aditivo (3+3) |
| La carta del restaurante | Composite (ComponenteCarta, Plato, SeccionCarta), variante segura |
Árbol parte-todo de profundidad libre con operaciones uniformes y recursivas |
| Menús y menús anidados | Composite (Menu, precio recursivo con descuento) |
El grupo también se vende; allMatch frente a anyMatch según el negocio |
| Extras de los platos | Decorator (ExtraPlato: doble queso, sin gluten, ración grande) |
Combinatoria opcional decidida por cada cliente en ejecución; 3 clases cubren 8 combinaciones |
| Reintentos y trazas de notificadores | Decorator técnico (NotificadorConReintentos, NotificadorConLogging) |
Responsabilidad transversal, opcional y componible sin tocar los canales |
| Flujo de confirmación de pedido | Facade (FachadaCheckout) |
Orquestación delicada copiada en cuatro clientes → escrita una vez tras una puerta simple |
| Seguimiento del pedido en las apps | Facade (FachadaSeguimiento) |
Mismo síntoma a escala menor; tipos propios de frontera |
| Mapa en tiempo real | Flyweight (IconoMarcador + FabricaIconos) |
13.000 marcadores repitiendo ~25 combinaciones de icono: de 400 MB a menos de 1 |
| Fotos de los platos | Proxy virtual (ProxyImagenPlato) |
Objeto caro y de uso escaso: se paga solo lo que se mira |
| Permisos del panel interno | Proxy de protección (ProxyProteccionGestionPedidos) |
El quién concentrado en un punto auditable; el servicio real, limpio de seguridad |
| Consultas de carta masivas | Proxy de caché (ProxyCacheCatalogo) |
Respuesta cara y estable, pedida miles de veces; invalidación diseñada a la vez |
| Trazas transversales multi-interfaz | Proxy dinámico (ManejadorLogging + java.lang.reflect.Proxy) |
Un solo handler para todas las interfaces; el mecanismo de los frameworks |
| Pedidos, carritos, líneas, direcciones... | Ninguno | Objetos en cantidades normales, interfaces que encajan, sin ejes multiplicándose: cualquier envoltorio aquí sería ruido |
Ese es el criterio de madurez que venimos repitiendo desde la balanza: no cuántos patrones tiene el sistema, sino que cada uno esté donde su síntoma lo justificó — y que el resto del código siga siendo simple.
Errores Comunes y Consejos
- Nombrar por mecánica, no por intención. Llamar "proxy" a un adaptador o "decorador" a una fachada compila igual, pero miente al lector sobre la promesa (¿traduce? ¿añade? ¿controla?). En esta familia, el nombre del patrón es documentación de primera clase: gástalo bien.
- Envolver por costumbre. Tras este módulo, la tentación es poner capas a todo. Cada envoltorio es una indirección que alguien depurará; sin síntoma (interfaz que no encaja, responsabilidad opcional, acceso que controlar), la mejor estructura es ninguna.
- Resolver con herencia lo que este módulo resuelve con composición. La subclase
MargaritaSinGlutenoConfirmacionPushsiempre estará a mano y siempre parecerá más rápida. Recuerda las dos explosiones (Decorator y Bridge las curan) antes de heredar la próxima variante. - Elegir Bridge cuando tocaba Adapter, o al revés. Si las piezas ya existen y no encajan: Adapter, y a otra cosa. Si las jerarquías las diseñas tú y van a crecer por dos ejes: Bridge. Diseñar "puentes" hacia SDKs ajenos o "adaptar" jerarquías propias son los dos disfraces del mismo despiste.
- Fachada como escondite. Si detrás de la fachada el subsistema es un nudo, el nudo sigue creciendo — ahora sin que nadie lo mire. La fachada corona un subsistema sano; no lo sustituye.
- Consejo: repite aquí el ejercicio del mapa que hiciste con los creacionales: lista cada frontera, cada jerarquía y cada multitud de tu sistema, y anota qué estructural (o qué ausencia) la gobierna y por qué. Las filas sin justificación son tus candidatas a simplificar; las fronteras sin adaptador y las orquestaciones copiadas, tus candidatas a mejorar.
Ejercicios
Ejercicio 1: diagnóstico exprés
Para cada situación nueva en PideYa, elige el patrón estructural (o ninguno) y justifícalo en una frase con su discriminante:
- El servicio de facturación de un partner exige recibir los pedidos en su formato EDI, muy distinto de nuestro modelo
Pedido. - Los cupones aplican recargos/descuentos apilables sobre el precio de un pedido: envío gratis, -10% primera compra, +0,50 € por hora punta, combinables según campaña.
- Las zonas de reparto se organizan en distritos que contienen barrios que contienen calles, y hay que saber si una dirección cae en una zona con recargo, a cualquier nivel.
- El nuevo widget "pide en un toque" para el móvil debe repetir el último pedido: hoy exige llamar a cinco servicios en orden.
- Cada uno de los 2 millones de clientes guarda su objeto
PreferenciasNotificacion, y el 97% tiene exactamente la configuración por defecto. - Queremos que el servicio de geocodificación solo se instancie (carga un índice de 2 GB) si algún pedido del día necesita resolver una dirección nueva.
Ejercicio 2: el careo en código
Sin contexto, un compañero encuentra esta clase y pregunta qué patrón es. ¿Qué le contestas y qué información adicional pedirías para decidir del todo?
public class ServicioRepartoVigilado implements ServicioReparto {
private final ServicioReparto interno;
private final Reloj reloj;
@Override
public AsignacionReparto asignar(Pedido pedido) {
long inicio = reloj.ahora();
AsignacionReparto a = interno.asignar(pedido);
Metricas.registrar("asignar", reloj.ahora() - inicio);
return a;
}
}Ejercicio 3: combinar con cabeza
El equipo de partners quiere exponer a terceros la consulta de cartas: API pública sencilla, sin revelar el modelo interno, con respuestas rápidas aunque el catálogo esté bajo carga, y sin que un partner sin contrato activo pueda consultar. Propón la combinación de patrones estructurales (mínimos) y el orden en que se atraviesan en una petición.
Soluciones
Solución 1:
- Adapter: formato ajeno e intocable en la frontera; un
AdaptadorFacturacionEditraducePedido→ EDI y nada más. - Decorator: recargos/descuentos opcionales, apilables y con orden significativo sobre una interfaz de precio — la cebolla de los extras, en el dominio de los cupones.
- Composite: parte-todo recursivo (distrito ⊃ barrio ⊃ calle) con una operación uniforme (
tieneRecargo(direccion)), respondida por hojas y propagada por grupos. - Facade: orquestación de varios subsistemas repetida desde un cliente más; una
FachadaPedidoRapido(que por dentro reutilizará el Prototype de "repetir pedido" del módulo 2). - Flyweight: multitud (2M) con estado masivamente repetido (97% idéntico e inmutable): una instancia compartida de las preferencias por defecto y objetos propios solo para quien personaliza.
- Proxy virtual: objeto carísimo de instanciar y posiblemente no usado; el proxy materializa el geocodificador al primer uso real.
Solución 2: la respuesta corta: "por mecánica es un envoltorio con la misma interfaz; por intención, medir llamadas está en la frontera Decorator/Proxy". Información para decidir: quién lo compone y con qué obligatoriedad. Si es la infraestructura quien lo interpone siempre en producción, sin que ningún cliente lo elija (control de oficio): llámalo proxy (de métricas/logging). Si es una capa opcional que cada punto de uso apila o no, combinándola con otras (reintentos, trazas): llámalo decorador técnico. También ayuda saber si existe una familia de envoltorios apilables (huele a Decorator) o si este es único y fijo delante del servicio real (huele a Proxy). Lo importante —dile también esto— es que la clase está bien: la duda es de etiqueta, no de diseño.
Solución 3: tres piezas, de fuera adentro: (1) Facade ApiPublicaCartas: superficie mínima para partners, con tipos propios de frontera (nada del modelo interno; el careo enseñó que interfaz nueva y más simple sobre un subsistema = fachada). (2) Proxy de protección sobre el servicio de catálogo: verifica el contrato activo del partner y deniega registrando (el quién). (3) Proxy de caché (ProxyCacheCatalogo, reutilizado): respuestas estables servidas de memoria (el cuántas veces). Orden de una petición: fachada → protección → caché → catálogo real, que devuelve el árbol Composite de la carta (cuarta pieza, ya existente, gratis). Protección antes que caché: un partner moroso no debe ni calentar la caché — el orden de envoltorios vuelve a ser semántica.
Conclusión
Ya no tienes siete patrones sueltos sino un sistema de decisión: la tabla de intenciones y costes, los dos ejes (¿cuántos objetos?, ¿qué pasa con la interfaz?), el careo de los cuatro envoltorios con sus cuatro preguntas, el flowchart para el caso típico y las combinaciones que aparecen solas cuando el diseño va bien. Y tienes el mapa de PideYa como demostración de la tesis del curso entero: cada patrón instalado donde un síntoma concreto lo justificó, ninguno donde no — con la fila "Ninguno" defendida con el mismo orgullo que las demás.
Con esto, dos tercios del catálogo GoF están en tu caja de herramientas: los creacionales resolvieron el nacimiento de los objetos; los estructurales, su anatomía — cómo se conectan, envuelven y organizan. Queda la tercera pregunta, la más dinámica de todas: con los objetos ya creados y bien conectados, ¿cómo colaboran en ejecución? ¿Quién avisa a quién cuando el pedido cambia de estado, cómo se deshace una acción, cómo recorre cocina una cola de comandas sin conocer su estructura, dónde vive el algoritmo que decide el reparto? Esa es la familia más numerosa del catálogo —once patrones de comportamiento— y PideYa está llena de conversaciones esperándolos. Nos vemos en la Introducción a los Patrones de Comportamiento.
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
