Interface Segregation Principle (ISP)

Interface Segregation Principle (ISP) – Wissenshäppchen #6

Das sechste Wissenshäppchen hat das Interface Segregation Principle als Thema.

Probeabo bei Audible (Affiliate)

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.

Interface Segregation Principle: Interface Iterable statt List in Java verwenden

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*.

Robert C. Martin - Clean Code: A Handbook of Agile Software Craftsmanship (Affiliate)*

Links

Probeabo bei Audible (Affiliate)

Navigation der Serie<< Liskov Substitution Principle (LSP) – Wissenshäppchen #5Dependency Inversion Principle (DIP) – Wissenshäppchen #7 >>

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax