5. Cohesión: definición y tipos en el diseño de clases y módulos

La cohesión mide cuán enfocada está una unidad de código en un propósito específico. Una alta cohesión mejora la comprensión, facilita la reutilización y simplifica las pruebas. Un módulo cohesivo cuenta una historia clara.

5.1 Concepto general de cohesión

Una clase o módulo muestra alta cohesión cuando todos sus elementos contribuyen a una responsabilidad principal. Si aparecen tareas ajenas o mezcladas, la cohesión se debilita. El concepto está directamente relacionado con el Principio de Responsabilidad Única de SOLID: cada componente debe tener un motivo único para cambiar.

5.2 Cohesión funcional

Se alcanza cuando todas las operaciones del módulo se orientan a una tarea bien definida. Es la forma más fuerte de cohesión porque la intención es inequívoca. Por ejemplo, un servicio que valida pedidos se concentra en reglas de negocio y delega otras tareas a colaboraciones externas.

class ValidadorPedido {
    boolean esValido(Pedido pedido) {
        return pedido.montoTotal().compareTo(BigDecimal.ZERO) > 0
            && pedido.lineas().stream().allMatch(this::lineaValida);
    }

    private boolean lineaValida(LineaPedido linea) {
        return linea.cantidad() > 0 && linea.precioUnitario().compareTo(BigDecimal.ZERO) > 0;
    }
}

5.3 Cohesión lógica

Ocurre cuando el módulo agrupa operaciones relacionadas de forma genérica, pero que sirven a propósitos distintos. Por ejemplo, una clase utilitaria que combina validaciones y conversiones. Es aceptable en casos concretos, aunque conviene separar responsabilidades cuando crece el número de funciones.

class UtilCliente {
    static boolean esEmailValido(String email) { /* ... */ }
    static boolean esCUITValido(String cuit) { /* ... */ }
    static ClienteDTO aDTO(Cliente cliente) { /* ... */ }
}

Si la cohesión lógica se expande, termina convirtiéndose en un repositorio de funcionalidades inconexas.

5.4 Cohesión secuencial

En este nivel, las operaciones están vinculadas por el flujo de datos: la salida de un método alimenta al siguiente. Es común en procesos de ETL o pipelines. Aunque puede ser eficaz, es importante garantizar que cada paso se mantenga enfocado.

class ProcesadorSuscripcion {
    Suscripcion procesar(PeticionSuscripcion peticion) {
        DatosCliente datos = normalizar(peticion);
        OfertaComercial oferta = seleccionarOferta(datos);
        return confirmarSuscripcion(datos, oferta);
    }

    // Cada método manipula datos relacionados con la suscripción
}

5.5 Cohesión coincidencial

Es la más débil: tareas dispares conviven sin relación clara. Suele aparecer por crecimiento improvisado o por falta de refactorización. Detectarla es clave para reorganizar el código y evitar confusión.

class GestorGeneral {
    void exportarCSV(List<Pedido> pedidos) { /* ... */ }
    void enviarRecordatorios() { /* ... */ }
    void limpiarCache() { /* ... */ }
}

La mezcla de responsabilidades resta claridad y dificulta localizar errores.

5.6 Impacto en la claridad del código

Una alta cohesión simplifica la lectura porque cada clase se puede explicar en pocas frases. También reduce las dependencias innecesarias, ya que un módulo cohesivo necesita menos información externa. En pruebas unitarias, permite aislar comportamientos con facilidad y reutilizar componentes en distintos contextos.

5.7 Mantener y elevar la cohesión

Para mantenerla se recomienda:

  • Revisar los motivos de cambio: si un componente se modifica por razones distintas, necesita dividirse.
  • Refactorizar frecuentemente, extrayendo clases o métodos cuando aparecen patrones mezclados.
  • Modelar el dominio siguiendo enfoques como Domain-Driven Design, que fomenta contextos delimitados.
  • Utilizar pruebas en Java como guía: si la configuración del test es compleja, la cohesión probablemente sea baja.