Interface Segregation Principle (ISP) – Wissenshäppchen #6
IT-Berufe-Podcast - A podcast by Stefan Macke - Lunedì
Categorie:
Das sechste Wissenshäppchen hat das Interface Segregation Principle als Thema. 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() und Repository.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