ISP — co to jest, zasada podziału interfejsów w programowaniu

Autor: IT Sectr Opublikowano: 2026-05-12 Czas czytania: 9 min

ISP (Interface Segregation Principle) — czwarta zasada SOLID, która stwierdza: klienci nie powinni zależeć od metod, których nie używają. Zasada została sformułowana przez Roberta Martina w kontekście projektowania interfejsów dla systemów obiektowych. Jak opisano w książce Clean Architecture (2017), zasada podziału interfejsów wymaga tworzenia wąsko wyspecjalizowanych interfejsów zamiast jednego uniwersalnego, co zmniejsza powiązania i upraszcza wprowadzanie zmian.

Najważniejsze

  • ISP — zasada podziału interfejsów, czwarta w SOLID
  • Klienci nie powinni zależeć od metod, których nie wywołują
  • Grube interfejsy (Fat Interfaces) zawierają metody nieistotne dla części klientów
  • Dzielenie interfejsów zmniejsza powiązania i zwiększa ponowne wykorzystanie kodu
  • ISP jest ściśle powiązany z SRP i Single Responsibility na poziomie interfejsów

Czym jest ISP (Interface Segregation Principle)?

ISP (Interface Segregation Principle) — zasada podziału interfejsów, która zabrania tworzenia „grubych” interfejsów z metodami nieużywanymi przez wszystkich klientów. Zamiast jednego interfejsu z dziesięcioma metodami projektuje się kilka małych interfejsów, każdy dla swojej grupy klientów.

Zasada została wprowadzona przez Roberta Martina jako rozwiązanie problemu „zanieczyszczania interfejsów”, gdy jedna klasa jest zmuszona implementować metody, których nie potrzebuje, tylko dlatego że są zadeklarowane w wspólnym interfejsie. W językach statycznie typowanych prowadzi to do pustych implementacji lub rzucania wyjątków — bezpośrednia oznaka naruszenia ISP.

ISP i SRP uzupełniają się: SRP dotyczy odpowiedzialności klasy, ISP dotyczy kontraktów interfejsów. SRP mówi „jedna klasa — jeden powód do zmiany”, ISP — „jeden interfejs — jeden scenariusz klienta”. Razem tworzą modułową architekturę, gdzie każdy element systemu ma wyraźne granice.

Grube interfejsy i ich konsekwencje

Fat Interface — interfejs zawierający więcej metod niż potrzebuje konkretny klient. Na przykład interfejs Worker z metodami work, eat, sleep. Robot-pracownik nie powinien implementować eat i sleep, ale jest zmuszony. Rozwiązanie — podział na Workable, Eatable, Sleepable. Każdy klient otrzymuje dokładnie to, czego potrzebuje.

W programowaniu mobilnym grube interfejsy występują w protokołach delegatów i DataSource. Jeden protokół może zawierać metody dla dwóch różnych scenariuszy (edycja + wyświetlanie), mimo że konkretny ekran używa tylko jednego z nich.

Jak działa zasada podziału interfejsów

Implementacja ISP zaczyna się od analizy klientów każdego interfejsu. Jeśli dwóch klientów używa różnych zestawów metod jednego interfejsu — interfejs należy podzielić. Każdy nowy interfejs grupuje metody wywoływane razem w ramach jednego scenariusza.

Mechanizm podziału: oryginalny interfejs jest dzielony na kilka wąskich, z których każdy dziedziczy wspólną część (jeśli istnieje). Klienci przełączają się na zależność od potrzebnego wąskiego interfejsu zamiast ogólnego. Klasy implementujące oryginalny interfejs teraz implementują tylko te wąskie interfejsy, które są im rzeczywiście potrzebne.

Ważne doprecyzowanie: stopień podziału jest określany przez liczbę klientów i ich scenariuszy. ISP nie wymaga maksymalnego dzielenia (mikrointerfejsy po jednej metodzie). Prowadziłoby to do nadmiernej złożoności. Celem jest wyeliminowanie zależności klientów od niepotrzebnych metod, a nie minimalizacja rozmiaru każdego interfejsu.

Oznaki naruszenia ISP

Główne oznaki naruszenia ISP obejmują: klasy implementujące interfejs z pustymi metodami (fikcyjna implementacja), rzucanie wyjątku UnsupportedOperationException w implementacjach, dużą liczbę parametrów lub typów zwracanych, które nie są używane przez część klientów, oraz częste zmiany interfejsu dotykające tylko część klientów.

W Android- development typowym przykładem naruszenia ISP jest interfejs OnItemClickListener, który zawiera metody dla kliknięcia, długiego kliknięcia i przesunięcia. Jeśli konkretny ekran używa tylko kliknięcia — pozostałe metody pozostają puste. Rozwiązanie — podział na OnItemClickListener, OnItemLongClickListener, OnItemSwipeListener.

W iOS-development naruszenie ISP przejawia się w delegatach UIKit: jeden protokół zawiera metody dla różnych stanów komponentu. UITableViewDelegate zawiera metody dla wyświetlania, zaznaczania, edycji i swipe-action. Często programiści implementują cały protokół z dziesięcioma pustymi metodami. Podział na kilka protokołów według grup odpowiedzialności rozwiązuje problem.

Zależność od nieużywanych metod

Problem nie polega tylko na estetyce kodu. Gdy interfejs się zmienia (dodawana jest nowa metoda), wszystkie klasy implementujące muszą być zaktualizowane — nawet te, które nie potrzebują nowej metody. W programowaniu mobilnym z dziesiątkami ekranów prowadzi to do kaskadowych zmian. ISP izoluje każdego klienta od zmian, które go nie dotyczą.

Niejawne naruszenie ISP występuje poprzez parametry konfiguracji. Jeśli metoda przyjmuje obiekt z dużą liczbą pól, a klient używa tylko 2-3 z nich — to sygnał do podziału. Alternatywa: kilka wyspecjalizowanych metod z minimalnym zestawem parametrów.

W Android-development ISP jest naruszany przy użyciu jednego SharedPreferencesManager do odczytu i zapisu wszystkich ustawień aplikacji. Fragment, który potrzebuje tylko odczytać motyw, otrzymuje zależność od globalnego menedżera z dziesięcioma metodami dla różnych typów danych. Podział na ThemePreferenceProvider, AuthPreferenceProvider, FeatureFlagProvider — zastosowanie ISP na poziomie usług konfiguracji. Każdy dostawca zawiera dokładnie te metody, których potrzebują jego klienci.

Przykłady ISP w aplikacjach mobilnych

Rozważmy przykład z Androida z interfejsem do pracy z danymi. Naruszenie ISP — jeden interfejs dla wszystkich operacji CRUD, mimo że nie wszyscy klienci potrzebują wszystkich operacji.

kotlin
// Naruszenie ISP: gruby interfejs
interface UserRepository {
    fun getAll(): List<User>
    fun getById(id: Int): User
    fun save(user: User)
    fun delete(id: Int)
}

// Po zastosowaniu ISP: wąskie interfejsy
interface UserReader {
    fun getAll(): List<User>
    fun getById(id: Int): User
}

interface UserWriter {
    fun save(user: User)
    fun delete(id: Int)
}

// ReadOnlyViewModel nie zależy od metod zapisu
class ReadOnlyViewModel(
    private val reader: UserReader
)

Przykład z iOS z podziałem protokołów do pracy z mediami:

swift
// Naruszenie ISP: jeden protokół dla całej pracy z mediami
protocol MediaService {
    func play(url: URL)
    func pause()
    func stop()
    func upload(data: Data) async -> URL
    func download(url: URL) async -> Data
}

// Po ISP: podział na protokoły według odpowiedzialności
protocol MediaPlayer {
    func play(url: URL)
    func pause()
    func stop()
}

protocol MediaTransfer {
    func upload(data: Data) async -> URL
    func download(url: URL) async -> Data
}

// PlayerViewModel nie zależy od metod ładowania
class PlayerViewModel {
    private let player: MediaPlayer
}

Praktyczny wniosek: ISP chroni klientów przed zmianami w niezależnych od nich częściach interfejsu. Podział UserRepository na UserReader i UserWriter oznacza, że zmiany w save nie wpływają na ReadOnlyViewModel i odwrotnie. Każdy klient jest izolowany od funkcjonalności, której nie używa, i nie wymaga zmian przy modyfikacji innych części systemu.

ISP i SRP — naturalna para. SRP określa, że klasa powinna mieć jeden powód do zmiany. ISP stosuje tę samą logikę do interfejsów: interfejs powinien obsługiwać jeden scenariusz klienta. Klasa może implementować kilka wąskich interfejsów (każdy odpowiada jednej odpowiedzialności), co jest czystsze niż jeden gruby interfejs z wieloma odpowiedzialnościami.

ISP i OCP są również powiązane: wąskie interfejsy łatwiej rozszerzać. Dodanie nowej metody do wąskiego interfejsu dotyka tylko jego klientów. Dodanie metody do grubego interfejsu dotyka wszystkich klientów — potencjalnie naruszając OCP, jeśli klienci są zmuszeni zmieniać swoją implementację.

ISP i DIP działają razem: DIP wymaga zależności od abstrakcji. ISP czyni te abstrakcje wąskimi i skupionymi. Zależność od szerokiego interfejsu to wciąż zależność od abstrakcji, ale „złej” abstrakcji z punktu widzenia ISP. Cztery zasady (SRP, OCP, ISP, DIP) tworzą „piramidę modułowości”: SRP i ISP określają granice, OCP i DIP — sposoby rozszerzania i łączenia.

Zastosowanie ISP w architekturze komponentowej

Architektura komponentowa w projektach mobilnych (moduły, funkcje, warstwy) korzysta z ISP na poziomie publicznych API. Każdy moduł eksportuje wąskie interfejsy dla swoich odbiorców, a nie jeden wspólny fasada. Pozwala to zmieniać wewnętrzną implementację modułu bez wpływu na odbiorców, którzy używają tylko części jego funkcjonalności.

W projektach Android z architekturą Clean Architecture ISP jest stosowany do UseCase: każdy UseCase to osobny interfejs z jedną metodą invoke lub execute. Klient (ViewModel) zależy tylko od tego UseCase, którego potrzebuje, a nie od całego repozytorium. To czyni zależności przejrzystymi i testowalnymi.

Często zadawane pytania

Czy ISP prowadzi do nadmiernej liczby interfejsów?

Tak, nadmierne dzielenie jest możliwe. ISP nie wymaga jednego interfejsu na każdą metodę. Kryterium: czy istnieje klient, który potrzebuje tylko części metod interfejsu. Jeśli wszyscy klienci używają wszystkich metod — interfejs nie wymaga dzielenia. Optymalny poziom podziału określają rzeczywiste scenariusze użycia.

Jak ISP stosuje się do parametrów funkcji?

ISP na poziomie parametrów oznacza: funkcja nie powinna przyjmować obiektów z dużą liczbą pól, jeśli używa tylko części z nich. Zamiast tego należy przekazywać tylko niezbędne dane lub używać wyspecjalizowanych interfejsów (na przykład interfejs Renderable zamiast pełnego User).

Czym ISP różni się od LSP?

LSP dotyczy poprawnego dziedziczenia i zgodności behawioralnej podtypów. ISP dotyczy projektowania interfejsów: klienci nie powinni zależeć od metod, których nie używają. LSP odpowiada na pytanie „czy można używać podklasy zamiast klasy bazowej?”, ISP — „czy klient potrzebuje całego interfejsu?”.

Jak ISP upraszcza testowanie?

Wąskie interfejsy upraszczają tworzenie obiektów mock: test tworzy mock z jedną-dwiema metodami, a nie z dziesięcioma. Im mniej metod w interfejsie, tym łatwiej zasymulować jego zachowanie. Zmniejsza to obciążenie poznawcze programisty testu i zmniejsza prawdopodobieństwo błędów w logice mocka.

Kiedy można odstąpić od ISP?

Jeśli interfejs jest stabilny i wszyscy klienci używają wszystkich metod — dzielenie jest zbędne. Typowy przykład: protokoły UIKit zaprojektowane przez Apple. Dzielenie ich jest ryzykowne, ponieważ UIKit oczekuje pełnej implementacji delegata. W takich przypadkach naruszenie ISP jest uzasadnione stabilnością API.

Podsumowanie

  • ISP (Interface Segregation Principle) — zasada podziału interfejsów, czwarta w SOLID
  • Klient nie powinien zależeć od metod, których nie używa
  • Grube interfejsy zmuszają klasy do implementowania niepotrzebnych metod jako pustych lub z wyjątkami
  • Podział interfejsów zmniejsza powiązania i izoluje klientów od zmian
  • ISP + SRP tworzą granice modułów: jedna odpowiedzialność — jeden wąski kontrakt
  • Testowanie mock jest prostsze: wąski interfejs wymaga mniej atrap
  • Optymalny podział określają rzeczywiste scenariusze klientów, a nie maksymalne dzielenie

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również