Los principios SOLID ofrecieron un vocabulario concreto para hablar de acoplamiento y cohesión. Cada principio ayuda a controlar estas fuerzas y, junto con patrones de diseño, sirven como guía para construir software flexible, preparado para el cambio sin sacrificar claridad.
Aplicar SOLID no es un fin en sí mismo: es el medio para mantener el equilibrio entre dependencias controladas y responsabilidades enfocadas. Cuando un proyecto adopta estos principios de forma consciente, las decisiones de diseño se vuelven más explícitas porque existen criterios objetivos para aceptar o rechazar cambios en la estructura.
El SRP establece que una clase debe tener un solo motivo para cambiar. Su objetivo esencial es elevar la cohesión, evitando que una unidad concentre tareas ajenas. En Java esto suele implicar segmentar entidades de dominio, servicios de aplicación y adaptadores de infraestructura:
class GeneradorResumenDiario {
private final RepositorioPedidos repositorioPedidos;
private final ServicioReporte servicioReporte;
GeneradorResumenDiario(RepositorioPedidos repositorioPedidos,
ServicioReporte servicioReporte) {
this.repositorioPedidos = repositorioPedidos;
this.servicioReporte = servicioReporte;
}
void generar(LocalDate fecha) {
List<Pedido> pedidos = repositorioPedidos.buscarPorFecha(fecha);
servicioReporte.crearResumen(pedidos);
}
}
El generador coordina actividades sin mezclar cómo se obtienen los pedidos o se produce el reporte. Cada colaborador mantiene su propia cohesión y motivo de cambio.
El OCP propone que los módulos estén abiertos a la extensión pero cerrados a la modificación. Al implementar nuevas variantes mediante herencia controlada o composición, se reduce la necesidad de tocar código existente, por lo que las dependencias se mantienen estables. Patrones como Estrategia, Decorador o incluso clases de configuración modular permiten cumplir este principio.
El DIP indica que las clases de alto nivel no deben depender de detalles de bajo nivel, sino de abstracciones. Esto evita que cambios en la infraestructura se propaguen al dominio. Es común apoyarse en inyección de dependencias o en interfaces implementadas por adaptadores específicos.
El patrón Fachada encapsula subsistemas complejos tras una interfaz simple. Su uso limita el acoplamiento al exponer un punto único de entrada y proteger a los consumidores de detalles internos que probablemente cambiarán.
Patrones como Comando, Servicio de Dominio o Adaptador ponen el foco en narrar una historia clara. Cada objeto describe una sola intención, lo que facilita comprender su comportamiento y refactorizarlo. Esta idea se complementa con el SRP y permite que cada colaborador se pruebe de manera aislada.
El siguiente ejemplo combina los principios y patrones mencionados. Define una fachada para orquestar el proceso de facturación y delega el cálculo de impuestos en estrategias extensibles:
interface EstrategiaImpuestos {
BigDecimal calcular(Pedido pedido);
}
class ImpuestoGeneral implements EstrategiaImpuestos {
public BigDecimal calcular(Pedido pedido) {
return pedido.subtotal().multiply(new BigDecimal("0.21"));
}
}
class ImpuestoReducido implements EstrategiaImpuestos {
public BigDecimal calcular(Pedido pedido) {
return pedido.subtotal().multiply(new BigDecimal("0.10"));
}
}
class FachadaFacturacion {
private final RepositorioPedidos repositorioPedidos;
private final RepositorioFacturas repositorioFacturas;
private EstrategiaImpuestos estrategiaImpuestos;
FachadaFacturacion(RepositorioPedidos repositorioPedidos,
RepositorioFacturas repositorioFacturas,
EstrategiaImpuestos estrategiaImpuestos) {
this.repositorioPedidos = repositorioPedidos;
this.repositorioFacturas = repositorioFacturas;
this.estrategiaImpuestos = estrategiaImpuestos;
}
Factura emitir(Long idPedido) {
Pedido pedido = repositorioPedidos.buscar(idPedido);
BigDecimal impuestos = estrategiaImpuestos.calcular(pedido);
Factura factura = Factura.crear(pedido, impuestos);
repositorioFacturas.guardar(factura);
return factura;
}
void cambiarEstrategia(EstrategiaImpuestos nuevaEstrategia) {
this.estrategiaImpuestos = nuevaEstrategia;
}
}
La fachada concentra el flujo de facturación (cohesión alta), protege al cliente de detalles de repositorios (acoplamiento reducido) y permite extender el cálculo de impuestos mediante nuevas estrategias sin modificar el código existente (OCP). Además, depende de la interfaz EstrategiaImpuestos (DIP), por lo que cualquier cambio en el código tributario se encapsula tras una abstracción.
Como en toda refactorización, conviene avanzar de forma incremental:
Al evaluar una iteración, utilice las siguientes preguntas como guía:
Alinearse con SOLID y emplear patrones apropiados permite dominar el equilibrio entre acoplamiento y cohesión. El resultado es una arquitectura preparada para evolucionar, con componentes comprensibles y conexiones controladas.