SOLID-Reihe

Solid-Reihe

Kai Papenhoff| Softwareentwickler | 12.04.2024

1. Single Responsibility Principle

1.1. Einleitung
Das Single Responsibility Principle ist aus allen SOLID Principles eines der am wenigsten verstandenen. Häufig wird dieses Prinzip missverstanden. Viele Entwickler interpretieren dieses Prinzip bestimmt, dass eine Funktion nur genau eine Sache ausführen darf. Und zwar, dass eine große Funktion in kleinere Teilfunktionen zerlegt wird. Dies entspricht allerdings nicht dem SRP.

1.2. Leitsatz
Das Prinzip der einzigen Verantwortung basiert auf folgender Kernaussage:

– „Separiere Code dann, wenn verschiedene Aktoren abhängig davon sind.“

1.3. Beispiel: Ungewollte Abhängigkeiten

Ein Beispiel aus einer Gehaltsabrechnungs-software liefert die Klasse Mitarbeiter. Sie enthält neben Mitarbeiterdaten drei Methoden:

Hierbei sind drei verschiedene Aktoren für jeweilige Spezifikationen verantwortlich.

 

 

  • Die Buchhaltung spezifiziert die Gehaltsberechnung.
  • Die Personalverwaltung spezifiziert die Stundenübersicht.
  • Die IT-Administration spezifiziert die Speichermethode.

 

Der Quellcode wird also gemeinsam in einer Klasse verwaltet.

public class Mitarbeiter
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Vorname { get; set; }
        public string Abteilung { get; set; }

        public void BerechneGehalt()
        {
            // Rufe RegulaereStunden ab.
            // Rufe Ueberstunden ab.
            // Berechne Gehalt.
            // Überweise Gehalt.
        }

        public void ErstelleStundenbericht()
        {
            // Rufe RegulaereStunden ab.
            // Erstelle Stundenbericht.
        }

        public void SpeichereMitarbeiter()
        {
            // Speichere Mitarbeiterdaten.
        }
    }

Stellen Sie sich nun vor, dass die Erstellung von Stundenübersichten und Gehaltsabrechnungen eine gemeinsam genutzte Funktion verwenden, um reguläre Arbeitsstunden zu ermitteln.

public class MitarbeiterStunden
{
    public Guid Id { get; set; }
    public Guid MitarbeiterId { get; set; }
    public DateTime Datum { get; set; }
    public int Stunden { get; set; }
    public int Ueberstunden { get; set; }

    public int RegulaereStunden()
    {
        return Stunden - Ueberstunden;
    }
}

 

 

Nun entscheidet die Personalverwaltung, dass die Berechnung regulärer Stunden der Stundenübersicht nach einer neuen Formel berechnet werden soll und aktualisiert den Berechnungsalgorithmus – ohne die Buchhaltung darüber zu informieren.

public double RegulaereStunden()
{
    return 6/5 * Stunden - 5/6 * Ueberstunden;
}

Nun möchte die Buchhaltung zum Monatsende Gehaltsabrechnungen durchführen und lässt den Algorithmus das Gehalt aller Mitarbeiter berechnen, ohne über die Änderung informiert worden zu sein.

Die Zahlungen an die Mitarbeiter werden nach dem neuen Berechnungsalgorithmus durchgeführt und erhalten etwa 20% mehr Gehaltszahlung.

Dieses Szenario ist ein gutes Beispiel für mangelhafte Modularisierung.

 

1.4. Beispiel: Häufige Konflikte

Stellen Sie sich nun vor, dass Personalverwaltung und Buchhaltung wegen neuer gesetzlichen Bestimmungen jeweils Änderungen in der Stundenübersicht bzw. Gehaltsabrechnung gleichzeitig durchführen müssen. Beim Zusammenführen beider Versionen in der Versionskontrolle werden höchstwahrscheinlich Konflikte entstehen, die mühselig gelöst werden müssen, da sich sämtlicher Quellcode in einer Klasse befindet.

 

1.5. Lösung: Methoden auslagern

Es gibt verschiedene Lösungen um das Problem zu beheben.

Eine ist es Methoden und Daten in Klassen auszulagern.

public class GehaltsBerechner
{
    public void BerechneGehalt()
    {
        // Rufe RegulaereStunden ab.
        // Rufe Ueberstunden ab.
        // Berechne Gehalt.
        // Überweise Gehalt.
    }
}

public class StundenBerichtErsteller
{
    public void ErstelleStundenbericht()
    {
        // Rufe RegulaereStunden ab.
        // Erstelle Stundenbericht.
    }
}

public class MitarbeiterSpeicherer
{
    public void SpeichereMitarbeiter()
    {
        // Speichere Mitarbeiterdaten
    }
}

public class MitarbeiterDaten
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Vorname { get; set; }
}

Vorteil ist, eine unabhängige Wartbarkeit, allerdings müssen drei Klassen zur Laufzeit instanziiert und verwaltet werden.

 

Eine Lösung dafür bietet das Fassade Entwurfsmuster. Die MitarbeiterFassade Klasse enthält nur wenig Quellcode und ist dafür zuständig untergeordnete Klassen zu Instanziieren und ihre Methoden zu delegieren.

public class MitarbeiterFassade
{
    private GehaltsBerechner gehaltsBerechner;
    private StundenBerichtErsteller stundenBerichtErsteller;
    private MitarbeiterSpeicherer mitarbeiterSpeicherer;

    public MitarbeiterFassade()
    {
        gehaltsBerechner = new GehaltsBerechner();
        this.stundenBerichtErsteller = new StundenBerichtErsteller();
        this.mitarbeiterSpeicherer = new MitarbeiterSpeicherer();
    }

    public void BerechneGehalt()
    {
        gehaltsBerechner.BerechneGehalt();
    }

    public void ErstelleStundenbericht()
    {
        stundenBerichtErsteller.ErstelleStundenbericht();
    }

    public void SpeichereMitarbeiter()
    {
        mitarbeiterSpeicherer.SpeichereMitarbeiter();
    }
}
public class MitarbeiterDaten
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Vorname { get; set; }
    public double Gehalt { get; set; }
    public MitarbeiterStunden MitarbeiterStunden { get; set; }
}

Es gibt jedoch Entwickler welche präferieren die wichtigsten Methoden näher an Daten zu koppeln.

public class Mitarbeiter
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Vorname { get; set; }
        public double Gehalt { get; set; }
        public MitarbeiterStunden MitarbeiterStunden { get; set; }

        private GehaltsBerechner gehaltsBerechner;
        private StundenBerichtErsteller stundenBerichtErsteller;
        private MitarbeiterSpeicherer mitarbeiterSpeicherer;

        public MitarbeiterFassade()
        {
            gehaltsBerechner = new GehaltsBerechner();
            this.stundenBerichtErsteller = new StundenBerichtErsteller();
            this.mitarbeiterSpeicherer = new MitarbeiterSpeicherer();
        }

        public void BerechneGehalt()
        {
            gehaltsBerechner.BerechneGehalt();
        }

        public void ErstelleStundenbericht()
        {
            stundenBerichtErsteller.ErstelleStundenbericht();
        }

        public void SpeichereMitarbeiter()
        {
            mitarbeiterSpeicherer.SpeichereMitarbeiter();
        }
    }
}

 

Kai Papenhoff| Softwareentwickler | 12.04.2024

Sie suchen einen Software Anbieter?
Sprechen Sie uns an