Home > Informatik > Begriffe und Konzepte > Prinzipien der OOP > Prinzip 1

1. Prinzip einer einzigen Verantwortung

Single Responsibility Principle (SRP)

Das Single Responsibility Principle (SRP) ist eines der fünf SOLID-Prinzipien der objektorientierten Programmierung. Es besagt, dass jedes Modul einer Software (in Java also die Pakte, Klassen und Methoden) genau eine klar abgegrenzte Verantwortung haben soll. Mit "Verantwortung" ist hier die Änderungsursache gemeint: Eine Klasse sollte nur aus

Sinn und Zweck dieses Prinzips ist es, den Code übersichtlich, wartbar und erweiterbar zu gestalten. Wenn eine Klasse mehrere unterschiedliche Aufgaben übernimmt, steigt die Gefahr von Abhängigkeiten und unerwünschten Seiteneffekten. Durch die Einhaltung des SRP wird jede Klasse fokussiert, leichter testbar und kann unabhängig von anderen Teilen des Systems weiterentwickelt werden.

Konkretes Beispiel

Das Waage-Projekt ohne Anwendung des SRP:

public class Waage
{
    private double gewicht, groesse;

    public Waage()
    {
    }
    
    private void zeigeFehler(String fehler)
    {
       System.out.println("Fehler: " + fehler);
    }

    public void setGewicht(double gewicht)
    {
        if (gewicht < 4)
        {
            zeigeFehler("Das Gewicht ist zu gering!");
            return;
        }
        
        if (gewicht > 130)
        {
           zeigeFehler("Das Gewicht ist größer als 130 kg!");
           return;
        }
        
        this.gewicht = gewicht;
    }

    public void setGroesse(double groesse)
    {
        if (groesse < 40)
        {
            zeigeFehler("Die Größe ist zu gering!");
            return;
        }
        
        if (groesse > 240)
        {
           zeigeFehler("Die Körpergröße ist größer als 240 cm!");
           return;
        }
        
        this.groesse = groesse;
    }   

    public double getGewicht()
    {
        return gewicht;
    }

    public double getGroesse()
    {
        return groesse;
    }

    public double getIdealgewicht()
    {
        return (groesse-100) * 0.9;
    }

    public double getDifferenz()
    {
        return gewicht - getIdealgewicht();
    }

    public void ausgeben()
    {
        System.out.printf("%-13s %6.2f kg%n", "Gewicht", getGewicht());
        System.out.printf("%-13s %6.2f kg%n", "Größe",   getGroesse());
        System.out.printf("%-13s %6.2f kg%n", "Idealgewicht", getIdealgewicht());
        System.out.printf("%-13s %6.2f kg%n", "Differenz", getDifferenz());

    }
}

Das SRP wird hier schon teilweise berücksichtigt. Zumindest schreiben die beiden Setter-Methoden setGewicht() und setGroesse() ihre Fehlermeldungen nicht gleich auf die Konsole (mit System.out.println()), sondern übergeben einen Fehlerstring an eine spezielle Methode zeigeFehler(). Wenn die Klasse Waage also einmal von einer graphischen Anwendung ohne Konsolenausgaben benutzt werden soll, muss die Methode zeigeFehler() entsprechend angepasst werden. Aber auch die Methode ausgeben() müsste dann überarbeitet werden.

public class TesteWaage
{
    public TesteWaage()
    {
        Waage waage = new Waage();
        double gewicht, groesse;
        
        waage.setGewicht(95.6);
        waage.setGroesse(189.3);   
        waage.ausgeben();
        
        waage.setGewicht(2.1);
        waage.setGroesse(12.2);   
        waage.ausgeben();
        
        waage.setGewicht(578.1);
        waage.setGroesse(456.2);   
        waage.ausgeben();
    }
    
    public static void main(String[] args)
    {
        new TesteWaage();
    }
}

Die Klasse Waage hat hier mehrere Aufgaben bzw. Verantwortungen:

1. Fachlogik/ Datenhaltung: Gewicht, Größe, Idealgewicht

2. Überprüfung der Eingaben (Gewicht, Größe zu klein / zu groß)

3. Ausgabe von Fehlermeldungen

4. Ausgabe der Daten und Berechnungen

Das Waage-Projekt unter Berücksichtung des SRP:

Hier wird die Klasse Waage in drei Klassen aufgespalten. Die Klasse Waage kümmert sich um die Fachlogik, die Klasse Fehlermeldung um die Fehlermeldungen, und die Klasse Ausgabe um die Ausgabe der Daten.

public class Waage
{
    private double gewicht;
    private double groesse;

    public Waage()
    {
    }

    public void setGewicht(double gewicht)
    {
        if (gewicht < 4)
            { 
                new Fehlermeldung("Gewicht zu gering (< 4 kg)."); 
                return; 
            }

        if (gewicht > 130)
            { 
                new Fehlermeldung("Gewicht zu groß (> 130 kg)."); 
                return; 
            }

        this.gewicht = gewicht;
    }

    public void setGroesse(double groesse)
    {
        if (groesse < 40)
            { 
                new Fehlermeldung("Größe zu gering (< 40 cm)."); 
                return; 
            }

        if (groesse > 240)
            { 
                new Fehlermeldung("Größe zu groß (> 240 cm)."); 
                return; 
            }

        this.groesse = groesse;
    }

    public double getGewicht()
    {
        return gewicht;
    }

    public double getGroesse()
    {
        return groesse;
    }

    public double getIdealgewicht()
    {
        return (groesse - 100) * 0.9;
    }

    public double getDifferenz()
    {
        return gewicht - getIdealgewicht();
    }
}

Für die Fehlermeldungen wird hier je ein Objekt der neuen Klasse Fehlermeldung erzeugt, und eine Ausgabe-Methode fehlt in dieser Klasse völlig. Daher kann sie in jeder Umgebung genutzt werden, in einer textorientierten Konsolen-Anwendung und auch in einer graphisch orientierten App.

public class Fehlermeldung
{
    public Fehlermeldung(String error)
    {
        System.out.println("Fehler: " + error);
    }
}

Das ist eine ganz einfache Klasse, die eine Fehlermeldung auf der Konsole anzeigt. Für eine Ausgabe der Meldungen in einer graphischen Umgebung müsste die Methode neu implementiert werden - die Hauptklasse Waage allerdings könnte so bleiben, wie sie ist.

public class Ausgabe
{
    public static void drucke(Waage w)
    {
        System.out.printf("%-13s %6.2f kg%n", "Gewicht",        w.getGewicht());
        System.out.printf("%-13s %6.2f cm%n", "Größe",          w.getGroesse());
        System.out.printf("%-13s %6.2f kg%n", "Idealgewicht",   w.getIdealgewicht());
        System.out.printf("%-13s %6.2f kg%n", "Differenz",      w.getDifferenz());
    }
}

Diese Klasse kann leicht an andere Umgebungen angepasst werden, indem die Methode drucke() durch weitere Methoden zum Beispiel für graphische Oberflächen ergänzt wird, oder indem die Klasse Ausgabe überarbeitet wird.

public class TesteWaage
{
    public TesteWaage()
    {
        Waage waage = new Waage();
       
        waage.setGewicht(95.6);
        waage.setGroesse(189.3);   
        Ausgabe.drucke(waage);
        
        waage.setGewicht(2.1);
        waage.setGroesse(12.2);   
        Ausgabe.drucke(waage);
        
        waage.setGewicht(578.1);
        waage.setGroesse(456.2);   
        Ausgabe.drucke(waage);
    }
    

    public static void main(String[] args)
    {
        new TesteWaage();
    }
}

Das ist das neue Testprogramm. Viel wurde hier nicht verändert, statt waage.drucke() finden wir jetzt Ausgabe.drucke(). Da die Methode drucke() in der Klasse Ausgabe als static definiert wurde, muss kein Objekt der Klasse Ausgabe angelegt werden, wenn man auf die drucke()-Methode zugreifen will. Man kann direkt mit Ausgabe.drucke(waage) die Werte des Waage-Objektes ausgeben lassen.

Refactoring

In dem Artikel "Refactoring" finden Sie zwei sehr schöne Beispiele, wie lange Methoden mit mehreren Aufgaben in mehrere kürzere Methoden mit je einer Aufgabe aufgeteilt wurden.

Vorteile des SRP

Nach Lahres [1] bietet die Einhaltung des Single Responsibility Principles folgende Vorteile:

Die Identifikation der Module (Klassen, Methoden), die angepasst werden müssen, ist recht einfach, da jedes Modul nur für eine Verantwortung zuständig ist.

Wenn mehrere Methoden für ein- und dieselbe Aufgabe zuständig sein sollten, dann müssten bei einer Änderung der Anforderungen (zum Beispiel Änderung des Ausgabemediums, andere Berechnungsmethoden, andere gültige Werte etc.) all diese Methoden überarbeitet werden.

Quellen:

  1. Lahres et al.: Objektorientierte Programmierung, Rheinwerk Computing 2021.
  2. Barnes, Kölling: Java lernen mit BlueJ - Objects first. Pearson-Verlag 2019.
  3. Ullenboom: Java ist auch eine Insel, Rheinwerk Computing 2023.
  4. MartinSRP: The Single Responsibility Principle.  Februar 1997
  5. Copeland: SOLID ist not SOLID - Five Object-oriented principles [...] everyone will hate". 2019 im Selbstverlag herausgegeben.