- Don’t Repeat Yourself (DRY) – Wissenshäppchen #1
- You Ain’t Gonna Need It (YAGNI) – Wissenshäppchen #2
- Single Responsibility Principle (SRP) – Wissenshäppchen #3
- Open Closed Principle (OCP) – Wissenshäppchen #4
- Liskov Substitution Principle (LSP) – Wissenshäppchen #5
- Interface Segregation Principle (ISP) – Wissenshäppchen #6
- Dependency Inversion Principle (DIP) – Wissenshäppchen #7
- Law of Demeter (LoD) – Wissenshäppchen #8
Das sechste Wissenshäppchen hat das Interface Segregation Principle als Thema.
Podcast: Play in new window | Download (Duration: 15:55 — 7.5MB)
Abonnieren: Apple Podcasts | Spotify | RSS
Inhalt
Das ISP ist das vierte SOLID-Prinzip.
The dependency of one class to another one should depend on the smallest possible interface.
Im Prinzip kann man das ISP so zusammenfassen: Verwende immer nur die kleinstmögliche Schnittstelle zu deinen Abhängigkeiten. Je mehr Funktionen eine Komponente an ihren Abhängigkeiten aufrufen kann, desto abhängiger wird sie von ihr. Wenn sich diese Funktionen nämlich ändern (z.B. die Signatur einer Methode), muss die nutzende Komponente neu kompiliert werden. Außerdem können Funktionen aufgerufen werden, die die nutzende Komponente weder benötigt, noch anwenden soll, z.B. die clear()
-Methode einer Liste, die sie eigentlich nur durchlaufen soll. Zuletzt müssen auch implementierende Klassen von zu großen Interfaces für sie unnötige Methoden implementieren, nur um der Schnittstelle zu entsprechen.
Erklärung
- Zu große Interfaces oder Basisklassen bieten evtl. Zugriff auf verschiedene Funktionalitäten, die nicht zusammengehören (z.B.
Repository.getUser()
undRepository.getArticle()
), oder über das benötigte/erlaubte Verhalten hinausgehen (z.B.Repository.deleteUser()
). - Komponenten, die von diesen Schnittstellen abhängen, können mit ihnen „zu viel“ machen und müssen angepasst (mind. rekompiliert) werden, wenn sich diese ändern, auch wenn sie die geänderte Funktionalität gar nicht nutzen.
- Komponenten, die die vorgegebenen Schnittstellen anbieten möchten, müssen mehr implementieren, als sie eigentlich wollen (oder dies nicht tun und das LSP mit einer
NotImplementedException
verletzen). - Tests von Komponenten, die Abhängigkeiten auf große Schnittstellen haben, werden schwieriger, da viele eigentlich unnötige Funktionen gestubbt oder gemockt werden müssen.
- Auch eine „große“ Klasse kann mehrere „kleine“ Interfaces anbieten und damit wohldefinierte „Fenster“ auf ihre Funktionalität anbieten.
Beispiel
Die Methode printEmployees()
im folgenden Beispiel erwartet eine List<Employee>
, obwohl sie diese lediglich durchlaufen können muss. Durch das „zu große“ Interface kann sie darüber hinaus jetzt auch die Liste leeren, was für den Aufrufer unschöne Konsequenzen hat.
public class ISP { public static void main(String[] args) { List<employee> employees = new ArrayList<>(); employees.add(new Employee("Stefan", "Macke")); employees.add(new Employee("Karl", "Meier")); employees.add(new Employee("Hans", "Georg")); printEmployees(employees); System.out.println(employees.size()); } private static void printEmployees(List</employee><employee> employees) { for (var employee : employees) { System.out.println(employee); } employees.clear(); } } // Ausgabe: // Stefan Macke // Karl Meier // Hans Georg // 0
Die Methode sollte stattdessen das kleinstmögliche Interface benutzen, das sie braucht, um ihre Aufgabe erfüllen zu können. In Java wäre das z.B. ´Iterable
private static void printEmployees(Iterable<employee> employees) { for (var employee : employees) { System.out.println(employee); } }
Der restliche Code funktioniert wie vorher, aber es sind nun keine modifizierenden Aufrufe mehr möglich, da das kleinere Interface die Methoden nicht anbietet.
Dynamisch typisierte Sprachen haben hier einen Vorteil, da sie dank Duck-Typing ohnehin kaum Basisklassen oder gar Interfaces benötigen. Sie rufen einfach nur die Methoden auf, die sie brauchen. Das kann allerdings auch zu impliziten Abhängigkeiten führen, die nicht vom Compiler geprüft werden, da der Zugriff auf die Klasse nicht durch ein Interface „geschützt“ ist.
Vorteile
- Kleinere Komponenten machen uns das Entwicklerleben einfach: klare Zuständigkeiten, einfachere Tests, weniger unnötige Abhängigkeiten.
- Durch klar definierte Schnittstellen wird die fehlerhafte oder unerlaubte Verwendung von Funktionen verhindert.
- Kleinere Schnittstellen entsprechen dem Single Responsibility Principle.
Nachteile
- Insgesamt steigt die Anzahl der Typen im System durch kleine Schnittstellen an (siehe auch SRP).
Literaturempfehlungen
Es wird schon langweilig: Auch beim vierten SOLID-Prinzip empfehle ich wieder Uncle Bobs Clean Code*.
Links
- Permalink zu dieser Podcast-Episode
- RSS-Feed des Podcasts
- Interface Segregation Principle
- ArticleS.UncleBob.PrinciplesOfOod (Artikel von Uncle Bob, Beispiel in C++)
- Interface-Segregation-Prinzip – Wikipedia
- Das Interface Segregation Principle (Deutsches Beispiel in C#)
Neueste Kommentare