SOLID Principles | Single Responsibility C# Tutorial

Single Responsibility Principle: Modulare Architektur mit C# & SOLID
Kai P. | Softwareentwickler
04/2024

Single Responsibility Principle

Das Single Responsibility Principle ist eines der am wenigsten verstandenen Prinzipien der SOLID-Grundsätze. Häufig wird es missverstanden. Viele Entwickler interpretieren dieses Prinzip so, dass eine Funktion nur genau eine Sache ausführen darf – also eine große Funktion in kleinere Teilfunktionen zerlegt wird. Dies entspricht jedoch nicht dem SRP.

Leitsatz

Das Prinzip der einzigen Verantwortung basiert auf folgender Kernaussage:

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

Beispiel: Ungewollte Abhängigkeiten

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

Hierbei sind drei verschiedene Akteure für die jeweiligen Spezifikationen verantwortlich:

UML-Diagramm mit Abteilungen und deren Zugriff auf Methoden der Mitarbeiter-Klasse per Abhängigkeitspfeile.

  1. Die Buchhaltung spezifiziert die Gehaltsberechnung.
  2. Die Personalverwaltung spezifiziert die Stundenübersicht.
  3. Die IT-Administration spezifiziert die Speichermethode.

Der Quellcode wird also gemeinsam in einer Klasse verwaltet.

1public class Mitarbeiter
2{
3    public Guid Id { get; set; }
4    public string Name { get; set; }
5    public string Vorname { get; set; }
6    public string Abteilung { get; set; }
7
8    public void BerechneGehalt()
9    {
10        // Rufe RegulaereStunden ab.
11        // Rufe Ueberstunden ab.
12        // Berechne Gehalt.
13        // Überweise Gehalt.
14    }
15
16    public void ErstelleStundenbericht()
17    {
18        // Rufe RegulaereStunden ab.
19        // Erstelle Stundenbericht.
20    }
21
22    public void SpeichereMitarbeiter()
23    {
24        // Speichere Mitarbeiterdaten.
25    }
26}‍

Nun stellen wir uns vor, dass die Erstellung von Stundenübersichten und Gehaltsabrechnungen eine gemeinsam genutzte Funktion verwenden, um reguläre Arbeitsstunden zu ermitteln.

1public class MitarbeiterStunden
2{
3    public Guid Id { get; set; }
4    public Guid MitarbeiterId { get; set; }
5    public DateTime Datum { get; set; }
6    public int Stunden { get; set; }
7    public int Ueberstunden { get; set; }
8
9    public int RegulaereStunden()
10    {
11        return Stunden - Ueberstunden;
12    }
13}‍
Diagramm zeigt, wie Personalverwaltung und Buchhaltung auf ReguläreStunden zugreifen und diese verwenden.

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

public double RegulaereStunden()
{
    return 6 / 5 * Stunden - 5 / 6 * Ueberstunden;
}‍
Diagramm mit grün markierter Personalverwaltung und Methode zur Erstellung der Stundenübersicht.

Wenn die Buchhaltung nun zum Monatsende Gehaltsabrechnungen durchführt und der Algorithmus das Gehalt aller Mitarbeiter berechnet, wird die Änderung ohne vorherige Information übernommen.

Diagramm mit grünem und rotem Block zur Darstellung aktiver und inaktiver Komponenten mit Beziehungen.

Die Zahlungen an die Mitarbeiter erfolgen nach dem neuen Berechnungsalgorithmus, was zu einer Erhöhung der Gehaltszahlungen um etwa 20 % führt.

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

Beispiel: Häufige Konflikte

Darstellung von Dev-, PV- und B-Branch mit Merge-Symbolen und Prüfzeichen für erfolgreichen und fehlerhaften Merge.

Nun stellen wir uns vor, dass die Personalverwaltung und die Buchhaltung wegen neuer gesetzlicher Bestimmungen jeweils Änderungen in der Stundenübersicht bzw. Gehaltsabrechnung gleichzeitig durchführen müssen. Beim Zusammenführen beider Versionen in der Versionskontrolle entstehen wahrscheinlich Konflikte, die mühsam gelöst werden müssen, da sich sämtlicher Quellcode in einer Klasse befindet.

Lösung: Methoden auslagern


Es gibt verschiedene Lösungen, um dieses Problem zu beheben. Eine davon ist, Methoden und Daten in Klassen auszulagern.

Klassendiagramm mit getrennten Komponenten für Gehalt, Stundenübersicht und Speicherung.
1public class GehaltsBerechner
2{
3    public void BerechneGehalt()
4    {
5        // Rufe RegulaereStunden ab.
6        // Rufe Ueberstunden ab.
7        // Berechne Gehalt.
8        // Überweise Gehalt.
9    }
10}
11
12public class StundenBerichtErsteller
13{
14    public void ErstelleStundenbericht()
15    {
16        // Rufe RegulaereStunden ab.
17        // Erstelle Stundenbericht.
18    }
19}
20
21public class MitarbeiterSpeicherer
22{
23    public void SpeichereMitarbeiter()
24    {
25        // Speichere Mitarbeiterdaten.
26    }
27}
28
29public class MitarbeiterDaten
30{
31    public Guid Id { get; set; }
32    public string Name { get; set; }
33    public string Vorname { get; set; }
34}‍

Der Vorteil dieser Herangehensweise ist eine unabhängige Wartbarkeit. Allerdings müssen zur Laufzeit drei Klassen instanziiert und verwaltet werden.

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

MitarbeiterFassade ruft Methoden in Gehalts-, Stunden- und Speicher-Klassen zur Datenverarbeitung auf.
1public class MitarbeiterFassade
2{
3    private GehaltsBerechner gehaltsBerechner;
4    private StundenBerichtErsteller stundenBerichtErsteller;
5    private MitarbeiterSpeicherer mitarbeiterSpeicherer;
6
7    public MitarbeiterFassade()
8    {
9        gehaltsBerechner = new GehaltsBerechner();
10        stundenBerichtErsteller = new StundenBerichtErsteller();
11        mitarbeiterSpeicherer = new MitarbeiterSpeicherer();
12    }
13
14    public void BerechneGehalt()
15    {
16        gehaltsBerechner.BerechneGehalt();
17    }
18
19    public void ErstelleStundenbericht()
20    {
21        stundenBerichtErsteller.ErstelleStundenbericht();
22    }
23
24    public void SpeichereMitarbeiter()
25    {
26        mitarbeiterSpeicherer.SpeichereMitarbeiter();
27    }
28}
29
30public class MitarbeiterDaten
31{
32    public Guid Id { get; set; }
33    public string Name { get; set; }
34    public string Vorname { get; set; }
35    public double Gehalt { get; set; }
36    public MitarbeiterStunden MitarbeiterStunden { get; set; }
37}‍

Es gibt jedoch Entwickler, die bevorzugen, die wichtigsten Methoden näher an die Daten zu koppeln.

Mitarbeiterklasse mit Methoden, die an spezialisierte Klassen wie Gehaltsberechner delegiert werden.
1public class Mitarbeiter
2{
3    public Guid Id { get; set; }
4    public string Name { get; set; }
5    public string Vorname { get; set; }
6    public double Gehalt { get; set; }
7    public MitarbeiterStunden MitarbeiterStunden { get; set; }
8
9    private GehaltsBerechner gehaltsBerechner;
10    private StundenBerichtErsteller stundenBerichtErsteller;
11    private MitarbeiterSpeicherer mitarbeiterSpeicherer;
12
13    public MitarbeiterFassade()
14    {
15        gehaltsBerechner = new GehaltsBerechner();
16        stundenBerichtErsteller = new StundenBerichtErsteller();
17        mitarbeiterSpeicherer = new MitarbeiterSpeicherer();
18    }
19
20    public void BerechneGehalt()
21    {
22        gehaltsBerechner.BerechneGehalt();
23    }
24
25    public void ErstelleStundenbericht()
26    {
27        stundenBerichtErsteller.ErstelleStundenbericht();
28    }
29
30    public void SpeichereMitarbeiter()
31    {
32        mitarbeiterSpeicherer.SpeichereMitarbeiter();
33    }
34}‍

Mehr zum Thema

Pfeil nach rechts (Verlinkung)
csharp-it-abteilung-vorteile-zukunftssichere-loesung
07/2025

C# für Unternehmen: Vorteile für IT-Abteilungen & Entwicklung

Blauer Pfeil nach rechts (Verlinkung)
FluentValidation in Blazor: Validierungslogik mit Custom Rules und Feldern
04/2025

Blazor FluentValidation | C# Validation Best Practices

Blauer Pfeil nach rechts (Verlinkung)
Liskov Principle: Schnittstellenfehler & Sicherheitslücken vermeiden
12/2024

Solid Reihe - Liskov Substitution Principle

Blauer Pfeil nach rechts (Verlinkung)
Open/Closed Principle: Erweiterbare Software dank klarer Struktur
11/2024

Solid Reihe - Open Close Principle

Blauer Pfeil nach rechts (Verlinkung)

Gemeinsam Großes schaffen

Devware GmbH verpflichtet sich, Ihre Privatsphäre zu schützen. Wir benötigen Ihre Kontaktinformationen, um Sie bezüglich unserer Produkte und Dienstleistungen zu kontaktieren. Mit Klick auf Absenden geben Sie sich damit einverstanden. Weitere Informationen finden Sie unter Datenschutz.
Vielen Dank für Ihr Vertrauen.
Unser Team prüft Ihre Anfrage sorgfältig und meldet sich in der Regel innerhalb von 48 Stunden bei Ihnen zurück.
Falls es besonders eilig ist, erreichen Sie uns auch telefonisch:
+ 49 (0) 202 478 269 0.
Da ist etwas schief gegangen beim Absenden des Formulars.