Czym jest Zasada Segregacji Interfejsów?
Zasada segregacji interfejsów mówi, że:
Klient nie powinien być zmuszany do zależności od interfejsów, których nie używa.
Innymi słowy, zamiast tworzyć obszerne, “grube” interfejsy (tzw. “fat interfaces”), powinniśmy dzielić je na mniejsze, bardziej specyficzne jednostki. Dzięki temu klasy implementują tylko te metody, których faktycznie potrzebują, co prowadzi do czystszego, bardziej elastycznego i łatwiejszego w utrzymaniu kodu.
Kluczowe Aspekty ISP
- Granularność interfejsów: Interfejsy powinny być małe i skupione na jednej, konkretnej odpowiedzialności lub roli.
- Unikanie “fat interfaces”: Duże, ogólne interfejsy zmuszają klasy do implementowania metod, które nie mają dla nich sensu. Prowadzi to do pustych implementacji lub, co gorsza, rzucania wyjątków.
- Poprawa elastyczności: Dzięki segregacji interfejsów możemy łatwiej rozszerzać system. Nowe klasy mogą implementować tylko te interfejsy, które są dla nich istotne, bez wpływu na istniejący kod.
- Zgodność z innymi zasadami SOLID: ISP doskonale uzupełnia Zasadę Pojedynczej Odpowiedzialności (SRP), ponieważ każdy interfejs ma jeden, jasno określony cel.
Przykład Naruszenia Zasady (Antywzorzec)
Wyobraźmy sobie system e-commerce, w którym mamy różne produkty. Tworzymy jeden ogólny interfejs Product_bad, który ma opisywać każdy możliwy produkt.
Diagram UML

Kod Naruszający Zasadę
Nasz “gruby” interfejs Product_bad zawiera metody dotyczące ceny, wagi oraz… systemu operacyjnego.
public interface Product_bad {
int getPrice();
void setPrice(int price);
int getWeight();
void setWeight(int weight);
String getOs();
void setOs(String os);
}Teraz spróbujmy zaimplementować ten interfejs dla dwóch różnych produktów: komputera i szkolenia online.
- Klasa
Computer_bad:
Dla komputera wszystkie metody mają sens, więc klasa implementuje cały interfejs.
public class Computer_bad implements Product_bad {
// ... Implementacje wszystkich metod: cena, waga, OS
}- Klasa
Training_bad:
Tutaj zaczyna się problem. Szkolenie ma cenę, ale nie ma wagi ani systemu operacyjnego. Jesteśmy jednak zmuszeni do zaimplementowania wszystkich metod z interfejsu.
public class Training_bad implements Product_bad {
@Override
public int getPrice() { return 100; }
@Override
public void setPrice(int price) { /* ... */ }
@Override
public int getWeight() {
throw new UnsupportedOperationException("Szkolenie nie ma wagi!");
}
@Override
public void setWeight(int weight) {
throw new UnsupportedOperationException("Szkolenie nie ma wagi!");
}
@Override
public String getOs() {
throw new UnsupportedOperationException("Szkolenie nie ma systemu operacyjnego!");
}
@Override
public void setOs(String os) {
throw new UnsupportedOperationException("Szkolenie nie ma systemu operacyjnego!");
}
}Problem z Naruszeniem ISP
Powyższy kod jest tykającą bombą zegarową.
- Zmuszanie do niepotrzebnych implementacji: Klasa
Training_badmusi implementować metody, które są dla niej bezsensowne. Rzucanie wyjątkówUnsupportedOperationExceptionjest anty-wzorcem, który może prowadzić do nieoczekiwanych błędów w czasie wykonania programu. - Brak elastyczności: Każdy kod, który operuje na obiektach typu
Product_bad, musi być napisany defensywnie, zakładając, że wywołanie metodygetWeight()może zakończyć się wyjątkiem. - Zanieczyszczenie kodu: Klasy stają się “zaśmiecone” pustymi lub rzucającymi wyjątki metodami, co zaciemnia ich prawdziwą odpowiedzialność i utrudnia zrozumienie oraz utrzymanie kodu.
Poprawne Zastosowanie Zasady (Refaktoryzacja)
Rozwiązaniem jest podział naszego “grubego” interfejsu na mniejsze, bardziej wyspecjalizowane jednostki, z których każda odpowiada za jedną, konkretną cechę produktu.
Diagram UML (po poprawie)

Poprawiony Kod
Tworzymy trzy małe, celowe interfejsy:
// Interfejs dla produktów, które mają cenę
public interface IProduct {
int getPrice();
void setPrice(int price);
}
// Interfejs dla produktów, które można dostarczyć (mają wagę)
public interface IDeliverable {
int getWeight();
void setWeight(int weight);
}
// Interfejs dla produktów komputerowych (mają OS)
public interface IComputer {
String getOs();
void setOs(String os);
}Teraz nasze klasy mogą implementować tylko te interfejsy, których naprawdę potrzebują.
- Klasa
Training:
Szkolenie ma tylko cenę, więc implementuje wyłącznie IProduct. Kod jest czysty i bezpieczny.
public class Training implements IProduct {
@Override
public int getPrice() { /* ... */ }
@Override
public void setPrice(int price) { /* ... */ }
}- Klasa
Computer:
Komputer ma wszystkie te cechy, więc implementuje wszystkie trzy interfejsy.
public class Computer implements IProduct, IDeliverable, IComputer {
@Override
public int getPrice() { /* ... */ }
@Override
public void setPrice(int price) { /* ... */ }
@Override
public int getWeight() { /* ... */ }
@Override
public void setWeight(int weight) { /* ... */ }
@Override
public String getOs() { /* ... */ }
@Override
public void setOs(String os) { /* ... */ }
}Dzięki temu podejściu klient (kod używający tych klas) może teraz zależeć tylko od potrzebnego mu interfejsu, np. funkcja obliczająca koszt dostawy będzie operować na IDeliverable, a system fakturowania na IProduct.
Porównanie Przykładów
| Aspekt | Wersja Naruszająca ISP | Wersja Zgodna z ISP |
| Struktura interfejsu | Jeden duży, ogólny interfejs Product_bad. | Wiele małych, specyficznych interfejsów (IProduct, IDeliverable). |
| Implementacja w klasach | Klasy są zmuszone implementować niepotrzebne metody, co prowadzi do wyjątków. | Klasy implementują tylko te interfejsy, których potrzebują. Brak zbędnego kodu. |
| Bezpieczeństwo kodu | Niskie. Klient używający Product_bad ryzykuje UnsupportedOperationException. | Wysokie. Klient zależy od konkretnego interfejsu, co gwarantuje istnienie potrzebnych metod. |
| Elastyczność i skalowalność | Niska. Dodanie nowego typu produktu (np. e-book) wymagałoby dalszych kompromisów. | Wysoka. Nowe klasy mogą swobodnie komponować potrzebne interfejsy. |
Podsumowanie
Zasada Segregacji Interfejsów to klucz do tworzenia modułowych i elastycznych systemów. Zamiast zmuszać klasy do noszenia “za dużego płaszcza” w postaci jednego, obszernego interfejsu, dajemy im zestaw mniejszych, dopasowanych narzędzi. Prowadzi to do czystszego kodu, mniejszej liczby błędów i znacznie łatwiejszego utrzymania oraz rozbudowy aplikacji. Stosowanie ISP sprawia, że nasza architektura staje się bardziej przemyślana i skalowalna.
Bibliografia
- Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall.
- Martin, R. C. (2017). Clean Architecture: A Craftsman’s Guide to Software Structure and Design. Prentice Hall.