Structural Patterns

Pregunta central

¿Cómo combinar objetos o componentes en estructuras mayores conservando flexibilidad? Structural Patterns agrupa soluciones basadas en composición, adaptación, wrapping e indirection. El interés no es tener más piezas: es poder cambiar una colaboración sin reescribir innecesariamente sus consumidores.

En la taxonomía GoF, esta familia incluye Adapter, Bridge, Composite, Decorator, Facade, Flyweight y Proxy. Sus intenciones se contrastan en el material académico de Principles of Software Construction, CMU.

Structural Design Patterns no equivale a internal-structure. La segunda rama trata organización interna y reglas arquitectónicas, como Layered o Clean. Un wrapper local puede coexistir con esas reglas; llamarlo structural no define toda la arquitectura. design-pattern explica el alcance y pattern-selection ayuda a evaluar su necesidad.

Siete soluciones representativas

Los ejemplos siguientes pertenecen a un subsistema propio de reportes. Cada uno ilustra una presión distinta; no se propone combinarlos todos.

Adapter

Adapter traduce una interfaz para que un consumidor colabore con un proveedor incompatible. El canonical desarrolla Object/Class Adapter y contrasta ese propósito con Facade y el rol arquitectónico en Ports and Adapters.

La señal es una incompatibilidad concreta que conviene encapsular. El riesgo es esconder diferencias semánticas: nombres y tipos compatibles no hacen equivalentes garantías, errores ni efectos.

Bridge

Permite variar abstracción e implementación de forma independiente. Nuestro diseñador de reportes ofrece modalidades de presentación, mientras los mecanismos de salida pueden ser archivo o impresión. Separar esos ejes mediante composición evita una subclase para cada combinación.

La señal son dimensiones de variación reales que evolucionan por razones distintas. El costo es mantener dos contratos y su colaboración. Una sola variación simple no justifica necesariamente esa separación.

La confusión habitual es llamar Bridge a cualquier interface o Adapter. Aquí interesa la independencia deliberada de ejes; adaptar un proveedor existente responde a otra presión, aunque los mecanismos puedan solaparse.

Composite

Ofrece tratamiento uniforme a elementos simples y composiciones en árbol. En nuestro reporte, una sección puede contener párrafos y subsecciones. El consumidor solicita una operación común sin recorrer manualmente cada tipo concreto.

La señal es una relación parte–todo recursiva con comportamiento compartido. El riesgo es forzar operaciones sin sentido sobre hojas, como agregar hijos a un párrafo, o permitir ciclos donde se esperaba un árbol.

Contener una lista no demuestra Composite. El contrato uniforme debe ayudar al consumidor y preservar reglas del dominio. Si las partes requieren operaciones esencialmente diferentes, una interfaz común excesiva puede ocultarlas.

Decorator

Decorator añade responsabilidades mediante wrappers componibles y un contrato compatible. La señal es comportamiento opcional cuya combinación no conviene expresar mediante una subclase por variante.

El canonical desarrolla orden, medición, identidad y costos de composición. La familia conserva la distinción central: wrapper → componente también aparece en Proxy o Adapter; intención y obligaciones permiten reconocer qué problema resuelve.

Facade

Facade ofrece acceso simplificado a un subsistema. Nuestro consumidor puede solicitar preparar una exportación sin coordinar directamente lectura de configuración, validación y empaquetado. La señal es una conversación repetida que revela demasiados detalles de cooperación interna.

La Facade mantiene esa coordinación detrás de una interfaz adecuada. El riesgo es transformarla en un objeto omnisciente que concentra todas las operaciones, o esconder resultados parciales que el consumidor necesita conocer.

No garantiza que el subsistema deje de estar disponible por otros contratos. Tampoco obliga a ofrecer un único método genérico para cualquier necesidad.

Flyweight

Comparte estado reutilizable para reducir el costo de muchas instancias pequeñas. En nuestro reporte, numerosas referencias pueden compartir una descripción inmutable de estilo; posición y contenido específico permanecen fuera de esa parte compartida.

La señal es un volumen medido de datos repetidos donde el ahorro justifica separar estado intrínseco y contexto. El riesgo es compartir información mutable que debería pertenecer a una instancia, o gastar más en búsquedas y coordinación que lo ahorrado.

No equivale a «poner todos los objetos en una cache». Deben definirse clave de identidad, lifetime y datos que no pueden compartirse; el beneficio requiere medición.

Proxy

Proxy representa otro objeto y media su acceso. Nuestro visor puede mantener un sustituto de una imagen pesada y cargarla cuando se necesita, conservando la conversación relevante para el consumidor.

La señal es controlar acceso, ubicación o adquisición de una capacidad sin exponer todos sus detalles al cliente. El riesgo es aparentar una operación local barata cuando realmente hay carga, bloqueo o comunicación remota.

Una referencia intermedia no prueba Proxy. Importa qué acceso controla y qué comportamiento observable agrega. Un proxy remoto necesita expresar fallos y latencia; el wrapper no los elimina.

Adapter GoF y el rol en Ports and Adapters

En ports-and-adapters, adapter designa un rol de frontera que traduce tecnología e interacción respecto de contratos de aplicación. Su alcance puede incluir HTTP, persistencia o un broker, con mapping, lifecycle y obligaciones de canal.

Ese rol arquitectónico no exige implementar GoF Adapter. Un endpoint HTTP puede interpretar una petición y llamar a un application port sin envolver una clase con interfaz incompatible. Un adaptador de database puede usar funciones, consultas o varios colaboradores.

A la inversa, un Adapter GoF puede resolver una incompatibilidad local dentro del núcleo, sin representar una frontera tecnológica. Un componente puede cumplir ambas funciones, pero debe explicarse cada intención.

La distinción evita deducir arquitectura por nombres. También permite revisar plugin-architecture: adaptar la API de una extensión puede ser una colaboración local, mientras discovery, activation y compatibilidad pertenecen a preocupaciones adicionales del host.

Facade y API Gateway

Facade no es sinónimo de API Gateway. Un gateway de microservices puede realizar routing, autenticación, políticas, limitación de tráfico y agregación, con consecuencias de disponibilidad y deployment. La guía de API gateways de Microsoft desarrolla esas responsabilidades.

Puede ofrecer una interfaz simplificada en algún aspecto, pero esa semejanza no agota su papel arquitectónico. Una Facade en memoria tampoco hereda garantías operativas de un gateway ni requiere una red.

En nuestro ejemplo, llamar ExportReport a una interfaz simplificada no decide si necesita un proceso independiente. El problema de acceso comprensible y el de coordinar llamadas remotas deben analizarse por separado.

Cuatro intenciones detrás de una forma parecida

PatternIntención principal
adapter-patternHacer colaborar interfaces incompatibles.
decorator-patternAgregar responsabilidades componibles.
facade-patternSimplificar el acceso a un subsistema.
proxy-patternRepresentar otro objeto y controlar su acceso.

Los cuatro pueden involucrar wrapping o indirection. Tener otro objeto dentro no identifica el patrón: hay que recuperar intent, problem, forces, collaboration y consequences. Facade ni siquiera requiere representar un único objeto o conservar su interfaz.

Una implementación puede combinar intenciones, por ejemplo una Facade que utiliza Adapters. Esa combinación debe explicar cada responsabilidad. La tabla orienta candidatos; pattern-selection mantiene la comparación con una solución directa.

Decorator frente a Proxy

Ambos pueden tener esta apariencia:

Client → Wrapper → Component

Estructura similar no significa el mismo patrón. Decorator intenta agregar responsabilidades combinables; Proxy intenta representar o mediar acceso. Un wrapper puede reunir intenciones, y clasificarlo requiere entender qué presión motivó cada una.

En nuestro ejemplo, añadir medición al exportador se evalúa por el comportamiento adicional; retrasar la adquisición de una imagen se evalúa por el acceso al objeto real. La forma de delegación no permite decidir sola.

Fowler muestra composición de Decorator con Command para agregar coordinación transaccional. Es un ejemplo de combinación por intención, no una recomendación para envolver toda operación. Decorated Command.

Consecuencias y revisión

Componer agrega tipos, contratos y pasos de ejecución. Deben conocerse errores, orden, identidad y cierre de recursos. Si dos wrappers creen ser dueños de la misma conexión, la combinación puede invalidar al consumidor aunque cada uno pase una prueba aislada.

Probar una adaptación requiere casos incompatibles y traducción de fallos; probar decoración requiere orden y conservación del comportamiento; probar compartir estado requiere independencia del contexto. No alcanza con verificar que el cliente llama al wrapper.

creational-patterns puede ensamblar estas estructuras y behavioral-patterns organizar lo que hacen. La decisión sigue siendo local al problema: un acceso directo estable puede ser más claro que una composición sin presión real. design-patterns conecta las familias sin convertirlas en requisitos para todo diseño.