10. Relación entre los principios y su aplicación conjunta

Los principios SOLID no viven aislados. Se refuerzan entre sí para crear una arquitectura robusta, legible y lista para el cambio. Entender cómo se conectan permite aplicarlos de manera estratégica en proyectos reales.

Cuando un equipo adopta SOLID de manera conjunta, la modularidad aparece como consecuencia natural: cada componente tiene una responsabilidad acotada, se extiende sin romper contratos existentes, respeta el polimorfismo, ofrece interfaces específicas y se apoya en dependencias estables.

10.1 Mapa conceptual de SOLID

Podemos imaginar SOLID como un circuito donde cada principio alimenta al siguiente:

  • SRP define responsabilidades claras.
  • OCP aprovecha esas responsabilidades para permitir extensiones controladas.
  • LSP garantiza que las extensiones sean compatibles con el contrato original.
  • ISP mantiene las interfaces precisas para que la sustitución sea viable.
  • DIP invierte las dependencias para que los componentes colaboren a través de abstracciones.

El circuito vuelve al inicio: al depender de abstracciones delgadas (ISP + DIP), es más sencillo mantener responsabilidades únicas (SRP).

10.2 Ejemplo integral en Java

Consideremos una plataforma de pedidos en línea. Queremos procesar pedidos, aplicar descuentos y notificar a clientes. Mostremos cómo se combinan los principios en un fragmento real.

interface CalculadoraDescuentos {
    BigDecimal calcular(Pedido pedido);
}

interface RepositorioPedidos {
    void guardar(Pedido pedido);
}

interface NotificadorPedidos {
    void notificar(Pedido pedido);
}

class ProcesadorPedidos {
    private final CalculadoraDescuentos calculadoraDescuentos;
    private final RepositorioPedidos repositorioPedidos;
    private final NotificadorPedidos notificadorPedidos;

    ProcesadorPedidos(CalculadoraDescuentos calculadoraDescuentos,
                      RepositorioPedidos repositorioPedidos,
                      NotificadorPedidos notificadorPedidos) {
        this.calculadoraDescuentos = calculadoraDescuentos;
        this.repositorioPedidos = repositorioPedidos;
        this.notificadorPedidos = notificadorPedidos;
    }

    void procesar(Pedido pedido) {
        BigDecimal descuento = calculadoraDescuentos.calcular(pedido);
        pedido.aplicarDescuento(descuento);
        repositorioPedidos.guardar(pedido);
        notificadorPedidos.notificar(pedido);
    }
}

En este ejemplo se aprecia la sinergia:

  • SRP: cada clase tiene una responsabilidad: calcular descuentos, guardar pedidos, notificar.
  • DIP: el servicio central depende de interfaces, no de implementaciones concretas.
  • ISP: cada interfaz representa una capacidad única.
  • OCP: nuevas reglas de descuento se agregan implementando CalculadoraDescuentos.
  • LSP: cualquier implementación de CalculadoraDescuentos puede sustituir a otra sin romper el flujo.

10.3 Secuencia recomendada para aplicar SOLID

  1. Identificar responsabilidades (SRP): dividir las clases grandes en piezas cohesivas.
  2. Extraer abstracciones (ISP): definir las interfaces mínimas que representan cada rol.
  3. Invertir dependencias (DIP): configurar colaboraciones a través de las interfaces creadas.
  4. Habilitar extensiones (OCP): permitir que nuevas variantes se incorporen sin cambiar el código existente.
  5. Asegurar sustitución (LSP): validar que las implementaciones cumplan con los contratos establecidos.

En la práctica estas etapas se iteran. Cada refactorización refuerza uno o más principios.

10.4 Anti-patrones al aplicar principios en aislamiento

  • Interfaz universal: romper ISP lleva a clases que desconocen SRP y hacen imposible cumplir LSP.
  • Herencias forzadas: ignorar LSP genera jerarquías difíciles de extender, violando OCP.
  • Abstracciones sin sentido: introducir interfaces “por default” sin un motivo dificulta la lectura y contradice SRP.
  • Controladores “dios”: acumular lógica en un único servicio viola SRP y bloquea la inversión de dependencias.

10.5 Caso práctico: evolución dirigida por SOLID

Un equipo que mantuvo un monolito de facturación usó SOLID para dividir el sistema en módulos. Primero separaron responsabilidades: facturación, pagos y reportes (SRP). Luego definieron interfaces para los servicios de integración con terceros (ISP). Posteriormente invirtieron dependencias, permitiendo usar diferentes proveedores de pago (DIP). Al incluir nuevos proveedores solo implementaron una interfaz, sin modificar el núcleo (OCP), y los tests de regresión confirmaron que todas las implementaciones respetaban el comportamiento esperado (LSP).

10.6 Métricas que evidencian la sinergia

  • Complejidad ciclomatica: desciende cuando SRP y ISP reducen tamaño de métodos.
  • Cobertura de pruebas: crece gracias a DIP y OCP, que permiten simular dependencias y añadir variantes sin alterar código probado.
  • Tiempo de ciclo: disminuye cuando el equipo puede incorporar nuevas reglas sin tocar módulos existentes.
  • Defectos regresivos: se reducen debido a que los contratos claros (LSP + ISP) limitan efectos colaterales.

10.7 Checklist para la adopción conjunta

  • Revisión de responsabilidades: ¿Cada clase tiene un propósito único y fácil de explicar?
  • Interfaces relevantes: ¿Cada contratista depende solo de los métodos que usa?
  • Nuevas variantes: ¿Agregar un comportamiento requiere modificar clases existentes o simplemente crear nuevas?
  • Pruebas polimórficas: ¿Las mismas pruebas se aplican a todas las implementaciones de una interfaz?
  • Dependencias explícitas: ¿Quién crea las instancias? ¿Hay inversión de dependencias o dependencias circulares?

10.8 Recomendaciones finales

  • Iterar con feedback: aplicar SOLID gradualmente, midiendo el impacto en métricas de calidad.
  • Adoptar pruebas automatizadas: SRP y DIP se consolidan cuando existen suites que validan cada colaboración.
  • Capacitar al equipo: compartir ejemplos concretos en Java ayuda a interiorizar los principios.
  • Documentar decisiones: explicar por qué se introduce una abstracción impide que vuelva a crecer la complejidad accidental.

La verdadera fuerza de SOLID aparece al aplicar los principios en conjunto. Su sinergia permite crear bases de código resilientes frente a cambios y aptas para escalar. En el siguiente tema estudiaremos errores comunes que surgen cuando se ignora esta relación entre los principios.