ISP (Interface Segregation Principle) ist das vierte SOLID-Prinzip, das besagt: Clients sollten nicht von Methoden abhängen, die sie nicht verwenden. Das Prinzip wurde von Robert Martin im Kontext des Schnittstellendesigns für objektorientierte Systeme formuliert. Wie im Buch Clean Architecture (2017) beschrieben, erfordert das Prinzip der Schnittstellentrennung die Erstellung eng spezialisierter Schnittstellen anstelle einer einzigen universellen, was die Kopplung reduziert und Änderungen vereinfacht.
Wichtigste Erkenntnisse
ISP (Interface Segregation Principle) ist das Prinzip der Schnittstellentrennung, das die Erstellung von „fetten“ Schnittstellen mit Methoden verbietet, die nicht von allen Clients verwendet werden. Anstelle einer Schnittstelle mit einem Dutzend Methoden werden mehrere kleine Schnittstellen entworfen, jede für ihre eigene Clientgruppe.
Das Prinzip wurde von Robert Martin als Lösung für das Problem der „Schnittstellenverschmutzung“ eingeführt, wenn eine Klasse gezwungen ist, Methoden zu implementieren, die sie nicht benötigt, nur weil sie in einer gemeinsamen Schnittstelle deklariert sind. In statisch typisierten Sprachen führt dies zu leeren Implementierungen oder Ausnahmefehlern — ein direktes Anzeichen einer ISP-Verletzung.
ISP und SRP ergänzen sich gegenseitig: SRP befasst sich mit der Verantwortung der Klasse, ISP mit den Schnittstellenverträgen. SRP sagt „eine Klasse — ein Grund für eine Änderung“, ISP sagt „eine Schnittstelle — ein Client-Szenario“. Zusammen bilden sie eine modulare Architektur, in der jedes Element des Systems klare Grenzen hat.
Fat Interface — eine Schnittstelle, die mehr Methoden enthält, als ein bestimmter Client benötigt. Zum Beispiel eine Worker-Schnittstelle mit den Methoden work, eat, sleep. Ein Roboterarbeiter sollte eat und sleep nicht implementieren, wird aber dazu gezwungen. Die Lösung besteht darin, in Workable, Eatable, Sleepable aufzuteilen. Jeder Client bekommt genau das, was er braucht.
In der mobilen Entwicklung finden sich fette Schnittstellen in Delegatenprotokollen und DataSource. Ein Protokoll kann Methoden für zwei verschiedene Szenarien (Bearbeitung + Anzeige) enthalten, obwohl ein bestimmter Bildschirm nur eines davon verwendet.
Die Implementierung von ISP beginnt mit der Analyse der Clients jeder Schnittstelle. Wenn zwei Clients unterschiedliche Methodensets einer Schnittstelle verwenden, sollte die Schnittstelle aufgeteilt werden. Jede neue Schnittstelle gruppiert Methoden, die innerhalb eines Szenarios zusammen aufgerufen werden.
Der Teilungsmechanismus: Die ursprüngliche Schnittstelle wird in mehrere schmale aufgeteilt, die jeweils den gemeinsamen Teil (falls vorhanden) erben. Clients wechseln zur Abhängigkeit von der benötigten schmalen Schnittstelle anstelle der allgemeinen. Klassen, die die ursprüngliche Schnittstelle implementieren, implementieren jetzt nur die schmalen Schnittstellen, die sie tatsächlich benötigen.
Eine wichtige Klarstellung: Der Grad der Aufteilung wird durch die Anzahl der Clients und ihrer Szenarien bestimmt. ISP erfordert keine maximale Aufteilung (Mikro-Schnittstellen mit je einer Methode). Dies würde zu einer übermäßigen Komplexität führen. Das Ziel ist es, die Abhängigkeit der Clients von unnötigen Methoden zu beseitigen, nicht die Größe jeder Schnittstelle zu minimieren.
Die wichtigsten Anzeichen einer ISP-Verletzung sind: Klassen, die eine Schnittstelle mit leeren Methoden implementieren (Dummy-Implementierung), das Auslösen von UnsupportedOperationException in Implementierungen, eine große Anzahl von Parametern oder Rückgabetypen, die von einigen Clients nicht verwendet werden, und häufige Schnittstellenänderungen, die nur einige Clients betreffen.
In der Android-Entwicklung ist ein typisches Beispiel für eine ISP-Verletzung die OnItemClickListener-Schnittstelle, die Methoden für Klick, langes Klicken und Wischen enthält. Wenn ein bestimmter Bildschirm nur Klick verwendet, bleiben die restlichen Methoden leer. Die Lösung besteht darin, in OnItemClickListener, OnItemLongClickListener, OnItemSwipeListener aufzuteilen.
In der iOS-Entwicklung zeigt sich die ISP-Verletzung in UIKit-Delegaten: Ein Protokoll enthält Methoden für verschiedene Komponentenzustände. UITableViewDelegate enthält Methoden für Anzeige, Auswahl, Bearbeitung und Wischaktion. Entwickler implementieren oft das gesamte Protokoll mit einem Dutzend leerer Methoden. Die Aufteilung in mehrere Protokolle nach Verantwortungsgruppen löst das Problem.
Das Problem ist nicht nur ästhetischer Natur. Wenn sich eine Schnittstelle ändert (eine neue Methode hinzugefügt wird), müssen alle implementierenden Klassen aktualisiert werden — selbst diejenigen, die die neue Methode nicht benötigen. In der mobilen Entwicklung mit Dutzenden von Bildschirmen führt dies zu kaskadierenden Änderungen. ISP isoliert jeden Client von Änderungen, die ihn nicht betreffen.
Eine implizite ISP-Verletzung erfolgt durch Konfigurationsparameter. Wenn eine Methode ein Objekt mit vielen Feldern akzeptiert und der Client nur 2-3 davon verwendet, ist dies ein Signal zur Aufteilung. Alternative: mehrere spezialisierte Methoden mit einem minimalen Parametersatz.
In der Android-Entwicklung wird ISP verletzt, wenn ein einzelner SharedPreferencesManager zum Lesen und Schreiben aller Anwendungseinstellungen verwendet wird. Ein Fragment, das nur das Theme lesen muss, erhält eine Abhängigkeit von einem globalen Manager mit einem Dutzend Methoden für verschiedene Datentypen. Die Aufteilung in ThemePreferenceProvider, AuthPreferenceProvider, FeatureFlagProvider — die Anwendung von ISP auf der Ebene der Konfigurationsdienste. Jeder Anbieter enthält genau die Methoden, die seine Clients benötigen.
Betrachten wir ein Android-Beispiel mit einer Schnittstelle zum Arbeiten mit Daten. ISP-Verletzung — eine Schnittstelle für alle CRUD-Operationen, obwohl nicht alle Clients alle Operationen benötigen.
// ISP-Verletzung: Fette Schnittstelle
interface UserRepository {
fun getAll(): List<User>
fun getById(id: Int): User
fun save(user: User)
fun delete(id: Int)
}
// Nach Anwendung von ISP: Schmale Schnittstellen
interface UserReader {
fun getAll(): List<User>
fun getById(id: Int): User
}
interface UserWriter {
fun save(user: User)
fun delete(id: Int)
}
// ReadOnlyViewModel hängt nicht von Schreibmethoden ab
class ReadOnlyViewModel(
private val reader: UserReader
)
Ein iOS-Beispiel mit Protokolltrennung für die Arbeit mit Medien:
// ISP-Verletzung: Ein Protokoll für die gesamte Medienarbeit
protocol MediaService {
func play(url: URL)
func pause()
func stop()
func upload(data: Data) async -> URL
func download(url: URL) async -> Data
}
// Nach ISP: Aufteilung in Protokolle nach Verantwortung
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 hängt nicht von Downloadmethoden ab
class PlayerViewModel {
private let player: MediaPlayer
}
Praktische Schlussfolgerung: ISP schützt Clients vor Änderungen in nicht zusammenhängenden Teilen der Schnittstelle. Die Aufteilung von UserRepository in UserReader und UserWriter bedeutet, dass Änderungen an save ReadOnlyViewModel nicht beeinflussen und umgekehrt. Jeder Client ist von der Funktionalität isoliert, die er nicht nutzt, und benötigt keine Änderungen, wenn andere Teile des Systems modifiziert werden.
ISP und SRP — ein natürliches Paar. SRP definiert, dass eine Klasse einen Grund für eine Änderung haben sollte. ISP wendet dieselbe Logik auf Schnittstellen an: Eine Schnittstelle sollte einem Client-Szenario dienen. Eine Klasse kann mehrere schmale Schnittstellen implementieren (jede entspricht einer Verantwortung), was sauberer ist als eine fette Schnittstelle mit mehreren Verantwortlichkeiten.
ISP und OCP sind ebenfalls verwandt: Schmale Schnittstellen lassen sich leichter erweitern. Das Hinzufügen einer neuen Methode zu einer schmalen Schnittstelle betrifft nur ihre Clients. Das Hinzufügen einer Methode zu einer fetten Schnittstelle betrifft alle Clients — was möglicherweise OCP verletzt, wenn Clients gezwungen sind, ihre Implementierung zu ändern.
ISP und DIP arbeiten zusammen: DIP erfordert die Abhängigkeit von Abstraktionen. ISP macht diese Abstraktionen schmal und fokussiert. Die Abhängigkeit von einer breiten Schnittstelle ist immer noch eine Abhängigkeit von einer Abstraktion, aber aus ISP-Sicht eine „schlechte“ Abstraktion. Vier Prinzipien (SRP, OCP, ISP, DIP) bilden die „Modularitätspyramide“: SRP und ISP definieren Grenzen, OCP und DIP definieren Erweiterungs- und Kopplungsmöglichkeiten.
Die Komponentenarchitektur in mobilen Projekten (Module, Features, Schichten) profitiert von ISP auf der Ebene der öffentlichen APIs. Jedes Modul exportiert schmale Schnittstellen für seine Verbraucher, anstatt einer einzigen gemeinsamen Fassade. Dies ermöglicht es, die interne Implementierung des Moduls zu ändern, ohne die Verbraucher zu beeinträchtigen, die nur einen Teil seiner Funktionalität nutzen.
In Android-Projekten mit Clean Architecture wird ISP auf UseCases angewendet: Jeder UseCase ist eine separate Schnittstelle mit einer einzigen invoke- oder execute-Methode. Der Client (ViewModel) hängt nur von dem UseCase ab, den er benötigt, nicht von einem gesamten Repository. Dies macht Abhängigkeiten transparent und testbar.
Häufig gestellte Fragen
Ja, übermäßige Aufteilung ist möglich. ISP erfordert nicht eine Schnittstelle pro Methode. Das Kriterium ist: Gibt es einen Client, der nur einen Teil der Schnittstellenmethoden benötigt? Wenn alle Clients alle Methoden verwenden, muss die Schnittstelle nicht aufgeteilt werden. Der optimale Grad der Aufteilung wird durch reale Nutzungsszenarien bestimmt.
ISP auf Parameter-Ebene bedeutet: Eine Funktion sollte keine Objekte mit vielen Feldern akzeptieren, wenn sie nur einen Teil davon verwendet. Stattdessen sollten nur die notwendigen Daten übergeben oder spezialisierte Schnittstellen verwendet werden (z. B. eine Renderable-Schnittstelle anstelle eines vollständigen User).
LSP befasst sich mit korrekter Vererbung und verhaltensmäßiger Subtyp-Kompatibilität. ISP befasst sich mit dem Schnittstellendesign: Clients sollten nicht von Methoden abhängen, die sie nicht verwenden. LSP beantwortet die Frage „Kann eine Unterklasse anstelle einer Basisklasse verwendet werden?“, ISP beantwortet „Benötigt der Client die gesamte Schnittstelle?“
Schmale Schnittstellen vereinfachen die Erstellung von Mock-Objekten: Der Test erstellt einen Mock mit ein oder zwei Methoden, nicht mit einem Dutzend. Je weniger Methoden eine Schnittstelle hat, desto einfacher ist es, ihr Verhalten zu simulieren. Dies reduziert die kognitive Belastung des Testentwicklers und verringert die Wahrscheinlichkeit von Fehlern in der Mock-Logik.
Wenn die Schnittstelle stabil ist und alle Clients alle Methoden verwenden, ist eine Aufteilung überflüssig. Ein typisches Beispiel: von Apple entwickelte UIKit-Protokolle. Diese aufzuteilen ist riskant, da UIKit eine vollständige Delegatenimplementierung erwartet. In solchen Fällen ist eine ISP-Verletzung durch die API-Stabilität gerechtfertigt.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch