8. Principio de Segregación de Interfaces (Interface Segregation Principle - ISP)

El Principio de Segregación de Interfaces (ISP, Interface Segregation Principle) plantea que nadie debería verse obligado a depender de métodos que no utiliza. Propone diseñar interfaces pequeñas, específicas para cada necesidad, en lugar de interfaces monolíticas que obligan a implementar comportamientos innecesarios.

Al aplicar ISP, las dependencias entre componentes se vuelven más claras y manejables. Esto reduce el acoplamiento, facilita las pruebas y evita que un cambio menor en una funcionalidad afecte a todas las implementaciones de una interfaz enorme.

8.1 Origen y motivación

ISP fue formalizado por Robert C. Martin a partir de experiencias en sistemas donde una sola interfaz representaba todas las operaciones del dominio. Esa práctica generaba implementaciones llenas de métodos sin uso, complicaba las pruebas y rompía el principio de responsabilidad única.

8.2 Síntomas de interfaces infladas

  • Métodos vacíos: clases concretas que implementan una interfaz pero dejan algunos métodos sin lógica.
  • Excepciones UnsupportedOperationException: señal de que la interfaz exige operaciones irrelevantes.
  • Interfaces “almacén general”: una interfaz concentra operaciones que responden a diferentes actores.
  • Alta fragilidad en cambios: modificar la interfaz obliga a recompilar y desplegar todos los consumidores.

8.3 Ejemplo de violación al ISP en Java

Imaginemos una interfaz para dispositivos multifunción que combinan impresión, escaneo y fax. Algunos dispositivos no soportan todas las funciones, pero la interfaz los obliga a implementarlas igual.

interface DispositivoMultifuncion {
    void imprimir(String documento);
    void escanear(String destino);
    void enviarFax(String numero);
}

class ImpresoraBasica implements DispositivoMultifuncion {
    public void imprimir(String documento) {
        System.out.println("Imprimiendo " + documento);
    }

    public void escanear(String destino) {
        throw new UnsupportedOperationException("Esta impresora no escanea");
    }

    public void enviarFax(String numero) {
        throw new UnsupportedOperationException("Esta impresora no envía fax");
    }
}

La clase ImpresoraBasica viola ISP porque depende de métodos que no necesita. Las excepciones indican que la interfaz está mal segregada.

8.4 Refactorización aplicando ISP

Dividimos la interfaz monolítica en contratos especializados para cada capacidad. Cada dispositivo implementa solo los métodos que realmente ofrece.

interface Impresora {
    void imprimir(String documento);
}

interface Escaner {
    void escanear(String destino);
}

interface Fax {
    void enviarFax(String numero);
}

class ImpresoraBasica implements Impresora {
    public void imprimir(String documento) {
        System.out.println("Imprimiendo " + documento);
    }
}

class CentroDeCopias implements Impresora, Escaner, Fax {
    public void imprimir(String documento) { /* ... */ }
    public void escanear(String destino) { /* ... */ }
    public void enviarFax(String numero) { /* ... */ }
}

El código resultante cumple el principio: las clases dependen solo de lo que utilizan, y cada interfaz representa una capacidad concreta.

8.5 Uso de adaptadores y composición

En sistemas existentes es común encontrar interfaces grandes. Una estrategia gradual consiste en crear adaptadores que implementen la interfaz antigua y deleguen en nuevas interfaces pequeñas. Así se evita romper el código cliente mientras se refactoriza.

8.6 Beneficios clave del ISP

  • Cambio controlado: una modificación impacta solo a las implementaciones relevantes.
  • Testabilidad: simplifica la creación de mocks y stubs específicos en pruebas.
  • Reutilización: las interfaces granulares se combinan para crear dispositivos o servicios más complejos.
  • Código autodocumentado: los nombres de las interfaces reflejan capacidades claras del dominio.

8.7 Ejemplo aplicado a servicios web

Supongamos una plataforma que expone un API para gestionar pedidos, pagos y envíos. Si todas las operaciones se agrupan en una sola interfaz, los consumidores que solo procesan pagos terminan viendo métodos de envíos innecesarios.

interface ApiComercio {
    Pedido crearPedido(Pedido pedido);
    void confirmarPago(Pago pago);
    void despacharEnvio(Envio envio);
}

class ProcesadorPagos implements ApiComercio {
    public Pedido crearPedido(Pedido pedido) {
        throw new UnsupportedOperationException("No crea pedidos");
    }

    public void confirmarPago(Pago pago) {
        // procesamiento del pago
    }

    public void despacharEnvio(Envio envio) {
        throw new UnsupportedOperationException("No gestiona envíos");
    }
}

8.8 Refactorización orientada a capacidades

Separar el API en interfaces especializadas permite que cada consumidor implemente o dependa solo de lo necesario.

interface ServicioPedidos {
    Pedido crearPedido(Pedido pedido);
}

interface ServicioPagos {
    void confirmarPago(Pago pago);
}

interface ServicioEnvios {
    void despacharEnvio(Envio envio);
}

class ProcesadorPagos implements ServicioPagos {
    public void confirmarPago(Pago pago) {
        // procesamiento del pago
    }
}

De esta manera se reduce el acoplamiento y se cumple ISP tanto del lado del servidor como del cliente.

8.9 Patrones y prácticas relacionadas

  • Interfaces de rol: cada interfaz representa el papel que un objeto puede desempeñar.
  • Inversión de dependencias: al combinar ISP con DIP, los componentes dependen de contratos pequeños y estables.
  • Limitación de alcance: usar interfaces internas o privadas en módulos para evitar que crezcan de forma descontrolada.
  • Revisión de arquitectura: revisar periódicamente las interfaces públicas para detectar responsabilidades mezcladas.

8.10 Recomendaciones finales

  • Escuchar al dominio: mapear las capacidades reales de los actores ayuda a definir interfaces precisas.
  • Refactorizar incrementando tests: antes de dividir una interfaz, asegurar cobertura de pruebas para evitar regresiones.
  • Documentar contratos: especificar qué método corresponde a qué caso de uso reduce malentendidos.
  • Combinar con microservicios: interfaces delgadas se alinean con servicios que tienen responsabilidades bien acotadas.

El Principio de Segregación de Interfaces evita interfaces infladas, mejora el acoplamiento y prepara el terreno para que el Principio de Inversión de Dependencias funcione correctamente. En el próximo tema abordaremos cómo elevar la abstracción para desacoplar módulos completos.