Principio de Segregación de Interfaces en Java

Descripción del Principio de Segregación de Interfaces

Una clase no debe verse obligada a implementar métodos que no utiliza. Los clientes solo deben depender de los métodos que realmente necesitan.

Escenario de Aplicación

Considere el siguiente diagrama UML
La clase Cliente1 utiliza la clase Proveedor1 a través de la interfaz IContratoGlobal, y la clase Cliente2 utiliza la clase Proveedor2 a través de la misma interfaz. Si IContratoGlobal contiene métodos que neither Cliente1 ni Cliente2 necesitan, entonces Proveedor1 y Proveedor2 se verían obligados a implementar funcionalidades innecesarias.
Siguiendo el principio de segregación de interfaces, la interfaz original debe dividirse en múltiples interfaces más específicas. En este caso, se dividiría en 3 interfaces independientes, donde cada cliente dependería únicamente de las interfaces que contienen los métodos que realmente utiliza.

Ejemplo Práctico

Implementación Sin Aplicar el Principio

public class DemoSinSegregacion {
    public static void main(String[] args) {
        
    }
}

// Contrato único con múltiples responsabilidades
interface IContratoGlobal {
    void ejecutarTarea1();
    void ejecutarTarea2();
    void ejecutarTarea3();
    void ejecutarTarea4();
    void ejecutarTarea5();
}

class Proveedor1 implements IContratoGlobal {
    public void ejecutarTarea1() {
        System.out.println("Proveedor1 ejecuta tarea 1");
    }
    public void ejecutarTarea2() {
        System.out.println("Proveedor1 ejecuta tarea 2");
    }
    public void ejecutarTarea3() {
        System.out.println("Proveedor1 ejecuta tarea 3");
    }
    public void ejecutarTarea4() {
        System.out.println("Proveedor1 ejecuta tarea 4");
    }
    public void ejecutarTarea5() {
        System.out.println("Proveedor1 ejecuta tarea 5");
    }
}   

class Proveedor2 implements IContratoGlobal {
    public void ejecutarTarea1() {
        System.out.println("Proveedor2 ejecuta tarea 1");
    }
    public void ejecutarTarea2() {
        System.out.println("Proveedor2 ejecuta tarea 2");
    }
    public void ejecutarTarea3() {
        System.out.println("Proveedor2 ejecuta tarea 3");
    }
    public void ejecutarTarea4() {
        System.out.println("Proveedor2 ejecuta tarea 4");
    }
    public void ejecutarTarea5() {
        System.out.println("Proveedor2 ejecuta tarea 5");
    }
}

class Cliente1 {  
    // Cliente1 utiliza Proveedor1 mediante IContratoGlobal
    // pero solo necesita los métodos 1, 2 y 3
    public void consumir1(IContratoGlobal contrato) {
        contrato.ejecutarTarea1();
    }
    public void consumir2(IContratoGlobal contrato) {
        contrato.ejecutarTarea2();
    }
    public void consumir3(IContratoGlobal contrato) {
        contrato.ejecutarTarea3();
    }
}

class Cliente2 {  
    // Cliente2 utiliza Proveedor2 mediante IContratoGlobal
    // pero solo necesita los métodos 1, 4 y 5
    public void consumir1(IContratoGlobal contrato) {
        contrato.ejecutarTarea1();
    }
    public void consumir4(IContratoGlobal contrato) {
        contrato.ejecutarTarea4();
    }
    public void consumir5(IContratoGlobal contrato) {
        contrato.ejecutarTarea5();
    }
}

En el código anterior, cuando Cliente1 depende de Proveedor1, no necesita los métodos ejecutarTarea4() y ejecutarTarea5(). De manera similar, cuendo Cliente2 depende de Proveedor2, no necesita ejecutarTarea2() y ejecutarTarea3(). En este caso, IContratoGlobal no representa la interfaz mínima requerida por los clientes.

Implementación Aplicando el Principio de Segregación

public class DemoConSegregacion {
    public static void main(String[] args) {
        Cliente1 cliente1 = new Cliente1();
        cliente1.consumir1(new Proveedor1());
        cliente1.consumir2(new Proveedor1());
        cliente1.consumir3(new Proveedor1());
        
        Cliente2 cliente2 = new Cliente2(); 
        cliente2.consumir1(new Proveedor2());
        cliente2.consumir4(new Proveedor2());
        cliente2.consumir5(new Proveedor2());
    }
}

// Interfaz específica para operación base
interface IOperacionBase {
    void ejecutarTarea1();
}

// Interfaz específica para grupo de operaciones A
interface IOperacionesGrupoA {
    void ejecutarTarea2();
    void ejecutarTarea3();
}

// Interfaz específica para grupo de operaciones B
interface IOperacionesGrupoB {
    void ejecutarTarea4();
    void ejecutarTarea5();
}

class Proveedor1 implements IOperacionBase, IOperacionesGrupoA {
    public void ejecutarTarea1() {
        System.out.println("Proveedor1 ejecuta tarea 1");
    }
    public void ejecutarTarea2() {
        System.out.println("Proveedor1 ejecuta tarea 2");
    }
    public void ejecutarTarea3() {
        System.out.println("Proveedor1 ejecuta tarea 3");
    }
}   

class Proveedor2 implements IOperacionBase, IOperacionesGrupoB {
    public void ejecutarTarea1() {
        System.out.println("Proveedor2 ejecuta tarea 1");
    }
    public void ejecutarTarea4() {
        System.out.println("Proveedor2 ejecuta tarea 4");
    }
    public void ejecutarTarea5() {
        System.out.println("Proveedor2 ejecuta tarea 5");
    }
}

class Cliente1 {  
    // Cliente1 utiliza Proveedor1 requiriendo tareas 1, 2 y 3
    public void consumir1(IOperacionBase operacion) {
        operacion.ejecutarTarea1();
    }
    public void consumir2(IOperacionesGrupoA operacion) {
        operacion.ejecutarTarea2();
    }
    public void consumir3(IOperacionesGrupoA operacion) {
        operacion.ejecutarTarea3();
    }
}

class Cliente2 {  
    // Cliente2 utiliza Proveedor2 requiriendo tareas 1, 4 y 5
    public void consumir1(IOperacionBase operacion) {
        operacion.ejecutarTarea1();
    }
    public void consumir4(IOperacionesGrupoB operacion) {
        operacion.ejecutarTarea4();
    }
    public void consumir5(IOperacionesGrupoB operacion) {
        operacion.ejecutarTarea5();
    }
}

Cliente1 depende de Proveedor1 implementando ejecutarTarea1(), ejecutarTarea2() y ejecutarTarea3().
Cliente2 depende de Proveedor2 implementando ejecutarTarea1(), ejecutarTarea4() y ejecutarTarea5().
Aplicando el principio de segregación, la interfaz original IContratoGlobal se dividió en tres interfaces especializadas:
La nueva interfaz IOperacionBase contiene el método ejecutarTarea1().
La interfaz IOperacionesGrupoA contiene los métodos ejecutarTarea2() y ejecutarTarea3().
La interfaz IOperacionesGrupoB contiene los métodos ejecutarTarea4() y ejecutarTarea5().
Proveedor1 implementa IOperacionBase e IOperacionesGrupoA.
Proveedor2 implementa IOperacionBase e IOperacionesGrupoB.
De esta manera, se cumple con el principio de segregación de interfaces.

Representación UML de las Relaciones

Etiquetas: java design-patterns solid-principles interface-segregation object-oriented-programming

Publicado el 9-21 21:48