SOLID #4 – ISP

Już dawno nie było wpisu o mnemoniku SOLID. Zapominalskich odsyłam do poprzednich wpisów (linki na dole strony). Dzisiaj przyszedł czas na przedstawienie czwartej zasady – zasady segregacji interfejsów.
Co to dokładnie znaczy? Zapraszam do krótkiej lecz treściwej lektury.

 

Interface Segregation Principle

Podstawą rozważań na temat tej zasady są interfejsy. Poprzez interfejs możemy rozumieć klasę abstrakcyjną z metodami abstrakcyjnymi lub zwykły interfejs jako byt programistyczny. W niniejszym artykule będę posługiwał się terminem interfejs mając na myśli obie te rzeczy.

Doskonale zdajemy sobie sprawę, że podczas implementacji interfejsu w konkretnej klasie jesteśmy zmuszeni do zaimplementowania każdej jego składowej. W takim razie, czy zdarzyło Ci się, że korzystając z jakiegoś interfejsu w nowo tworzonej klasie, nie wypełniłeś ciała którejś z jego metod? Jeżeli tak, to złamałeś zasadę ISP. A mówi ona:



 

 

Dlatego jeżeli implementujesz pewien interfejs i okazuje się, że część metod nie jest Ci w niej potrzebna, zdecydowanie powinieneś przemyśleć strukturę tego interfejsu. Może się okazać, że będziesz zmuszony do podziału jednego interfejsu na kilka mniejszych.

 

 

Praktyczny przykład

Jak to zwykle bywa, wszystko łatwiej zrozumieć na przykładzie. Posłużę się lekko zmodyfikowanym kodem z poprzedniego wpisu o mnemoniku SOLID. Oto przykładowy kod:

public interface IVehicle
{
    double Combustion { get; set; }
    string Name { get; set; }
    int WheelsCount { get; set; }

    void About();
}

public class Car : IVehicle
{
    public string Type { get; set; }
    public double Combustion { get; set; }
    public string Name { get; set; }
    public int WheelsCount { get; set; }

    public void About()
    {
        Console.WriteLine($"I'm {Type} - {Name} - with {Combustion}L combustion and {WheelsCount} wheels count!");
    }
}

public class ElectricCar : IVehicle
{
    public int BatteriesCount { get; set; }

    public void About()
    {
        Console.WriteLine($"I'm  {Name} - with {WheelsCount} wheels count and {BatteriesCount} batteries count!");
    }

    public double Combustion { get; set; }
    public string Name { get; set; }
    public int WheelsCount { get; set; }
}


internal class Program
{
    private static void Main()
    {
        var electricCar = new ElectricCar();
        var car = new Car();

        car.Name = "Tesla";
        car.WheelsCount = 4;
        car.Type = "Muscle Car";
        car.Combustion = 10.5f;

        electricCar.Name = "Tesla";
        electricCar.WheelsCount = 4;
        electricCar.BatteriesCount = 10;

        electricCar.About();
        car.About();

        Console.WriteLine();
        Console.ReadKey();
    }
}

 

Wynik działania:

 

Wszystko działa poprawnie. Zwróć proszę uwagę na kod programu, a dokładnie na klasę ElectricCar. Właściwość Combustion musi być w niej zaimplementowana pomimo tego, że nie będzie używana. Możliwość przypisania wartości do właściwość mimo że tego nie będzie wykorzystywana jest co najmniej dziwne. Koniecznie trzeba coś z tym zrobić.

 

Sposobów na poprawienie tego problemu jest wiele. Jednym z nich jest utworzenie nowego interfejsu dla pojazdów elektrycznych dziedziczący po IVehicle – przedtem należy zmodyfikować ten interfejs.

public interface IVehicle
{
    string Name { get; set; }
    int WheelsCount { get; set; }
    void About();
}

public interface IElectricVehicle : IVehicle
{
    int BatteriesCount { get; set; }
}

public class Car : IVehicle
{
    public string Type { get; set; }
    public double Combustion { get; set; }
    public string Name { get; set; }
    public int WheelsCount { get; set; }

    public void About()
    {
        Console.WriteLine($"I'm {Type} - {Name} - with {Combustion}L combustion and {WheelsCount} wheels count!");
    }
}

public class ElectricCar : IElectricVehicle
{
    public string Name { get; set; }
    public int WheelsCount { get; set; }
    public int BatteriesCount { get; set; }

    public void About()
    {
        Console.WriteLine($"I'm  {Name} - with {WheelsCount} wheels count and {BatteriesCount} batteries count!");
    }

}

internal class Program
{
    private static void Main()
    {
        var electricCar = new ElectricCar();
        var car = new Car();

        car.Name = "Tesla";
        car.WheelsCount = 4;
        car.Type = "Muscle Car";
        car.Combustion = 10.5f;

        electricCar.Name = "Tesla";
        electricCar.WheelsCount = 4;
        electricCar.BatteriesCount = 10;

        electricCar.About();
        car.About();

        Console.WriteLine();
        Console.ReadKey();
    }
}

 

 

Podsumowanie

Przykład choć naiwny, przedstawił główne założenie zasady ISP. Należy zwracać uwagę na tworzone interfejsy, aby klasy je wykorzystujące implementowały w pełni metody oraz inne byty udostępniane przez tworzony przez nas interfejs.

 

 

Linki do poprzednich części:

S Zasada pojedynczej odpowiedzialności (Single Responsibility Principle – SRP)
O Zasada otwarte – zamknięte – (Open-Closed Principle – OCP)
LZasada podstawiania Liskov – (Liskov Substitution Principle – LSP)

Mam coś dla Ciebie

Zapisz się do mojego newslettera, a ja prześlę Ci zbiór kilkunastu praktycznych wskazówek dla programisty aplikacji mobilnych.

Menu