Principio de segregacion de interfaces - FernandoCalmet/csharp-essential GitHub Wiki

El principio de segregación de la interfaz establece que ningún cliente debe verse obligado a depender de métodos que no utiliza. Entonces, esta es la definición básica que podemos leer en muchos artículos diferentes, pero ¿qué significa esto realmente?

Imaginemos que estamos comenzando una nueva función en nuestro proyecto. Comenzamos con algo de código y de ese código emerge una interfaz con las declaraciones requeridas. Poco después, el cliente decide que quiere otra característica similar a la anterior y decidimos implementar la misma interfaz en otra clase. Pero ahora, como consecuencia, no necesitamos todos los métodos de esa interfaz, solo algunos de ellos. Por supuesto, tenemos que implementar todos los métodos, que no deberíamos tener que hacerlo, y ese es el problema y donde el ISP nos ayuda mucho.

Básicamente, el ISP establece que debemos reducir los objetos de código a la implementación requerida más pequeña, creando así interfaces con solo las declaraciones requeridas. Como resultado, una interfaz que tiene muchas declaraciones diferentes debe dividirse en interfaces más pequeñas.

Ejemplo inicial

Hay vehículos que podemos conducir y hay otros con los que podemos volar. Pero hay autos que podemos conducir y volar (sí, esos están en oferta). Entonces, queremos crear una estructura de código que admita todas las acciones para un solo vehículo, y vamos a comenzar con una interfaz:

public interface IVehicle
{
    void Drive();
    void Fly();
}

Ahora bien, si queremos desarrollar un comportamiento para un automóvil multifuncional, esta interfaz nos va a ser perfecta:

public class MultiFunctionalCar : IVehicle
{
    public void Drive()
    {
        //actions to start driving car
        Console.WriteLine("Drive a multifunctional car");
    }
    public void Fly()
    {
        //actions to start flying
        Console.WriteLine("Fly a multifunctional car");
    }
}

Esto está funcionando muy bien. Nuestra interfaz cubre todas las acciones requeridas.

Pero ahora, queremos implementar la clase Car y la clase Airplane también:

public class Car : IVehicle
{
    public void Drive()
    {
        //actions to drive a car
        Console.WriteLine("Driving a car");
    }
    public void Fly()
    {
        throw new NotImplementedException();
    }
}
public class Airplane : IVehicle
{
    public void Drive()
    {
        throw new NotImplementedException();
    }
    public void Fly()
    {
        //actions to fly a plane
        Console.WriteLine("Flying a plane");
    }
}

Ahora podemos ver cuál es el problema con la interfaz IVehicle. Contiene solo una declaración requerida por cada clase. El otro método, que no es obligatorio, se implementa para generar una excepción. Esa es una mala idea porque deberíamos estar escribiendo nuestro código para hacer algo y no solo para lanzar excepciones. Además, tendríamos que hacer un esfuerzo adicional para documentar nuestra clase para que los usuarios sepan por qué no deberían usar el método no implementado. Una muy mala idea.

Entonces, para solucionar este problema, vamos a refactorizar nuestro código y escribirlo de acuerdo con el ISP.

Implementando el ISP en la solución actual

Lo primero que vamos a hacer es dividir nuestra IVehicleinterfaz:

public interface ICar
{
    void Drive();
}
public interface IAirplane
{
    void Fly();
}

Como resultado, nuestras clases pueden implementar solo los métodos que necesitan:

public class Car : ICar
{
    public void Drive()
    {
        //actions to drive a car
        Console.WriteLine("Driving a car");
    }
}
public class Airplane : IAirplane
{
    public void Fly()
    {
        //actions to fly a plane
        Console.WriteLine("Flying a plane");
    }
}
public class MultiFunctionalCar : ICar, IAirplane
{
    public void Drive()
    {
        //actions to start driving car
        Console.WriteLine("Drive a multifunctional car");
    }
    public void Fly()
    {
        //actions to start flying
        Console.WriteLine("Fly a multifunctional car");
    }
}

Incluso podemos usar una interfaz de nivel superior si queremos en una situación en la que una sola clase implementa más de una interfaz:

public interface IMultiFunctionalVehicle : ICar, IAirplane
{
}

Una vez que tengamos nuestra interfaz de nivel superior, podemos implementarla de diferentes maneras. El primero es implementar los métodos requeridos:

public class MultiFunctionalCar : IMultiFunctionalVehicle
{
    public void Drive()
    {
        //actions to start driving car
        Console.WriteLine("Drive a multifunctional car");
    }
    public void Fly()
    {
        //actions to start flying
        Console.WriteLine("Fly a multifunctional car");
    }
}

O si ya hemos implementado la clase Coche y la clase Avión, podemos usarlas dentro de nuestra clase usando el patrón decorador:

public class MultiFunctionalCar : IMultiFunctionalVehicle
{
    private readonly ICar _car;
    private readonly IAirplane _airplane;
    public MultiFunctionalCar(ICar car, IAirplane airplane)
    {
        _car = car;
        _airplane = airplane;
    }
    public void Drive()
    {
        _car.Drive();
    }
    public void Fly()
    {
        _airplane.Fly();
    }
}

¿Cuáles son los beneficios del principio de segregación de interfaz?

Podemos ver en el ejemplo anterior que la interfaz más pequeña es mucho más fácil de implementar debido a que no es necesario implementar métodos que nuestra clase no necesita.

Por supuesto, debido a la simplicidad de nuestro ejemplo, podemos crear una sola interfaz con un solo método dentro. Pero en los proyectos del mundo real, a menudo creamos una interfaz con múltiples métodos, lo cual es perfectamente normal siempre que esos métodos estén muy relacionados entre sí. Por lo tanto, nos aseguramos de que nuestra clase necesite todas estas acciones para completar su tarea.

Otro beneficio es que el Principio de segregación de la interfaz aumenta la legibilidad y el mantenimiento de nuestro código. Estamos reduciendo la implementación de nuestra clase solo a las acciones requeridas sin ningún código adicional o innecesario.

Conclusión

Para resumir este artículo, deberíamos esforzarnos en crear interfaces más pequeñas mientras desarrollamos nuestro proyecto. Sí, podemos terminar con muchas interfaces diferentes al final, pero desde nuestro punto de vista, esto es mucho mejor que tener algunas interfaces grandes que pueden obligarnos a implementar métodos no requeridos en nuestras clases.