7. Relación entre acoplamiento y cohesión: dos fuerzas opuestas pero complementarias

El diseño estructural del software se mantiene estable cuando logra armonizar dos fuerzas: el acoplamiento, que mide la dependencia entre unidades, y la cohesión, que evalúa la unidad interna de cada componente. Más que opuestas, son complementarias: mejorar una afecta a la otra y el objetivo es alcanzar un equilibrio que favorezca la mantenibilidad.

7.1 Compreder el equilibrio en el diseño

En todo sistema las piezas necesitan colaborar. Reducir el acoplamiento a cero es imposible y, si se lograra, eliminaría cualquier coordinación. Del mismo modo, la cohesión absoluta aislada rompería los flujos de negocio. El equilibrio implica permitir colaboraciones controladas entre componentes altamente cohesivos para que la intención del código permanezca clara y sus cambios controlados.

7.2 Relación con el Principio de Responsabilidad Única

El Principio de Responsabilidad Única, base del conjunto SOLID, refuerza la idea de cohesión: cada clase debe cambiar por un único motivo. Cuando se respeta esta premisa, el acoplamiento necesario se sostiene mediante contratos bien definidos, evitando dependencias implícitas o atajos que erosionan el equilibrio.

7.3 Figuras extremas a evitar

Los extremos suelen ser fáciles de reconocer:

  • Alta cohesión con acoplamiento excesivo: aparece al dividir correctamente las responsabilidades pero mantener dependencias cruzadas, generando cascadas de cambios.
  • Bajo acoplamiento con cohesión difusa: sucede cuando se crean interfaces genéricas para evitar dependencias sin definir claramente las responsabilidades internas.
  • Bajo acoplamiento y baja cohesión: experiencia frecuente en arquitecturas improvisadas, donde cada clase es autónoma pero desordenada.
  • Acoplamiento alto y cohesión alta: común en monolitos que resuelven un dominio acotado; puede ser aceptable a corto plazo, pero limita la escalabilidad.

7.4 Visualizar la tensión en el dominio

Representar el dominio con diagramas de contexto ayuda a decidir qué componentes deben colaborar estrechamente y cuáles pueden mantener interfaces más abstractas. Al mapear las relaciones se identifica cuándo un servicio está asumiendo demasiadas responsabilidades, o cuándo un módulo necesita un canal de comunicación más directo para evitar complejidad accidental.

7.5 Ejemplo práctico en Java

El siguiente fragmento muestra un servicio de facturación que coordina cuatro colaboradores. La cohesión del servicio es alta (solo calcula y confirma facturas), mientras el acoplamiento se mantiene bajo gracias a contratos explícitos.

class ServicioFacturacion {
    private final RepositorioFactura repositorioFactura;
    private final CalculadoraImpuestos calculadoraImpuestos;
    private final ServicioInventario servicioInventario;
    private final IntegracionContable integracionContable;

    ServicioFacturacion(RepositorioFactura repositorioFactura,
                        CalculadoraImpuestos calculadoraImpuestos,
                        ServicioInventario servicioInventario,
                        IntegracionContable integracionContable) {
        this.repositorioFactura = repositorioFactura;
        this.calculadoraImpuestos = calculadoraImpuestos;
        this.servicioInventario = servicioInventario;
        this.integracionContable = integracionContable;
    }

    Factura emitir(Pedido pedido) {
        servicioInventario.reservar(pedido);
        Factura factura = Factura.desde(pedido, calculadoraImpuestos);
        repositorioFactura.guardar(factura);
        integracionContable.registrar(factura);
        return factura;
    }
}

Si en el futuro se agrega un nuevo medio de notificación, el servicio principal no debería modificarse. La extensión ocurre mediante la inyección de una nueva interfaz, preservando la cohesión y controlando el aumento del acoplamiento.

7.6 Estrategias para mantener el equilibrio

Algunas prácticas simplifican la construcción de sistemas sostenibles:

  • Aplicar patrones que expresen colaboraciones concretas, como Facade para encapsular subsistemas y coordinar dependencias.
  • Utilizar interfaces orientadas al dominio, evitando tipos genéricos que confundan la intención.
  • Controlar el flujo de dependencias mediante inyección, evitando que los objetos construyan sus propias colaboraciones.
  • Reforzar la cohesión interna con pruebas unitarias en Java, que actúan como red de seguridad al refactorizar.

7.7 Uso de métricas combinadas

Monitorear el acoplamiento y la cohesión de forma conjunta permite detectar desequilibrios. Valores elevados de LCOM junto con un alto conteo de dependencias indican clases que necesitan particionarse y redistribuir sus colaboraciones. Herramientas como SonarQube, IntelliJ o Eclipse IDE ofrecen tableros que cruzan estas métricas para visualizar los puntos críticos.

7.8 Impacto en la mantenibilidad y operación diaria

Cuando el equilibrio se sostiene, los equipos perciben los beneficios: los cambios se localizan, la revisión de código se enfoca en decisiones de negocio y el despliegue reduce riesgos. Por el contrario, un desequilibrio se manifiesta en ciclos de liberación irregulares, deuda técnica acumulada y dificultad para incorporar nuevas personas al proyecto.

7.9 Lista de control para decisiones de diseño

Antes de cerrar un incremento, conviene repasar la siguiente lista:

  • ¿Se introdujo una dependencia nueva? ¿Puede aislarse tras una interfaz específica del dominio?
  • ¿Se mantiene una historia clara dentro de cada clase o aparecen pequeñas desviaciones que requieren refactorización?
  • ¿Las colaboraciones principales están documentadas mediante pruebas o diagramas para guiar al equipo?
  • ¿Los cambios se pueden desplegar de manera parcial sin alterar componentes no relacionados?

Mantener el equilibrio entre acoplamiento y cohesión es un trabajo continuo. Requiere observación, refactorizaciones frecuentes y una comprensión profunda del dominio de negocio. Al practicar estas ideas, la arquitectura se mantiene evolutiva y preparada para los desafíos futuros.