W kolejnym, przedostatnim artykule z serii o zasadach SOLID przedstawiam Wam Zasadę segregacji interfejsów (Interface Segregation Principle – ISP).

Tę regułę można streścić w następujących słowach:

Wiele dedykowanych interfejsów jest lepsze niż jeden ogólny

Robert C. 'Uncle Bob’ Martin

Innymi słowy: lepiej posiadać klasę implementującą wiele interfejsów niż taką, która implementuje jeden, większy interfejs z wieloma deklaracjami metod. Inna definicja ISP mówi nam o tym, że:

Klienci nie powinni być zmuszeni do zależności od interfejsów, których nie używają.

Robert C. 'Uncle Bob’ Martin

W programowaniu klient oznacza moduł/klasę podlegającą danej klasie nadrzędnej/modułowi bądź korzystający z interfejsu. Duży, czyli tzw. tłusty interfejs (ang. fat interface) może zawierać deklaracje metod, których nie wszyscy klienci powinni implementować.

ISP — po co i dlaczego?

Załóżmy, że chciałbym napić się kawy z automatu ☕, który serwuje różne ich rodzaje. Wrzucam monetę i wybieram, dajmy na to, espresso bez żadnych dodatków typu mleko czy cukier. Okazuje się potem, że mimo, iż o to nie prosiłem, automat wydał mi kawę nie dość, że posłodzoną to jeszcze z mlekiem i posypką czekoladową 😠. Albo inaczej: automat zawiesza się przy próbie wyboru innej kawy niż ze wszystkimi dodatkami. Wniosek jest prosty – maszyna za każdym razem uruchamia (lub próbuje uruchamiać) funkcje, które w obu przypadku były niepotrzebne. Niezależnie od wyboru rodzaju napoju.

ISP zakłada, że jeśli mielibyśmy do czynienia z dużym interfejsem, którego klienci nie są w stanie w pełni zaimplementować jego wszystkich metod, to już lepiej taki interfejs podzielić na mniejsze. Wówczas klasy implementując wiele mniejszych interfejsów mogą wprowadzić implementacje tylu funkcji, ile potrzebują.

ISP na przykładzie

Przykład zastosowania opisywanego zagadnienia nie będzie miał nic wspólnego z kawą. No chyba, że chodzi o symbol filiżanki z tym napojem ☕, który lata temu przypisano do języka Java .

Mamy dwie drukarki 🖨️: jedna jest urządzeniem wielofunkcyjnym, która oprócz drukowania może dokumenty zeskanować lub skserować, druga natomiast to „zwykła” drukarka z podstawową funkcją druku. Obie implementują interfejs Printer.

public interface Printer {

    void print(String fileName);

    void scan();

    void photocopy();
}

public class MultifunctionalPrinter implements Printer {

    @Override
    public void print(String fileName) {
        System.out.println("Printing file " + fileName + "...");
    }

    @Override
    public void scan() {
        System.out.println("Scanning in progress...");
    }

    @Override
    public void photocopy() {
        System.out.println("Photocopy in progress...");
    }
}

public class SimplePrinter implements Printer {

    @Override
    public void print(String fileName) {
        System.out.println("Printing file " + fileName + "...");
    }

    @Override
    public void scan() {
        throw new UnsupportedOperationException("No scan function in this printer");
    }

    @Override
    public void photocopy() {
        throw new UnsupportedOperationException("No photocopy function in this printer");
    }
}
 

Jak widać, „zwykła” drukarka jest zmuszona do implementacji metod, których tak naprawdę nie potrzebuje, skoro ma tylko i wyłącznie drukować dokumenty. Wywołanie metody scan lub photocopy będzie skutkowało pojawieniem się komunikatu o błędzie z informacją o nieobsługiwaniu danej funkcji. Nie pozostaje nic innego, jak wdrożyć ISP i podzielić interfejs Printer na mniejsze.

public interface Printer {

    void print(String fileName);
}


public interface AdditionalPrinterFunctions {

    void scan();

    void photocopy();
}


public class SimplePrinter implements Printer {

    @Override
    public void print(String fileName) {
        System.out.println("Printing file " + fileName + "...");
    }
}


public class MultifunctionalPrinter implements Printer, AdditionalPrinterFunctions {

    @Override
    public void print(String fileName) {
        System.out.println("Printing file " + fileName + "...");
    }

    @Override
    public void scan() {
        System.out.println("Scan in progress...");
    }

    @Override
    public void photocopy() {
        System.out.println("Photocopy in progress...");
    }
}
 

Metody scanphotocopy przeniosłem do oddzielnego interfejsu. Teraz klasa SimplePrinter po zaimplementowaniu interfejsu Printer nie jest zmuszana do implementacji metod, które nie są jej potrzebne.

Podsumowanie

Zastosowanie ISP może uczynić nasz kod bardziej elastycznym, spójnym, jak również „konkretnym”. Dzięki tworzeniu wielu mniejszych, dedykowanych interfejsów zamiast dużych (grubych), ogólnych, możemy uniknąć wymuszania na klientach implementacji niepotrzebnych metod.


0 komentarzy

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *