Zum Inhalt springen

Single-Responsibility-Prinzip

aus Wikipedia, der freien Enzyklopädie
Dies ist die aktuelle Version dieser Seite, zuletzt bearbeitet am 4. Januar 2026 um 07:10 Uhr durch imported>Invisigoth67 (typo).
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)

Das Single-Responsibility-Prinzip (SRP, {{Modul:Vorlage:lang}} Modul:Vorlage:lang:103: attempt to index field 'wikibase' (a nil value)) ist eine Entwurfsrichtlinie in der Softwarearchitektur.

Definition

Eine weit verbreitete, aber fehlerhafte Annahme ist, dass SRP aussagt, dass jede Klasse nur eine fest definierte Aufgabe zu erfüllen habe.<ref>Robert C. Martin: Clean Architecture: A Craftsman’s Guide to Software Structure and Design. 1. Auflage. Prentice Hall, 2017, ISBN 978-0-13-449416-6, S. 62.</ref>

Der Ausdruck wurde von Robert C. Martin in einem Teilartikel gleichen Namens in seiner Publikation Principles of Object Oriented Design<ref>Robert C. Martin: The Principles of OOD. 11. Mai 2005, abgerufen am 22. April 2014 (Lua-Fehler in Modul:Multilingual, Zeile 153: attempt to index field 'data' (a nil value)).</ref> eingeführt:

“There should never be more than one reason for a class to change.”

„Es sollte nie mehr als einen Grund geben, eine Klasse zu ändern.“

– <templatestyles src="Person/styles.css" />Robert C. Martin: SRP: The Single Responsibility Principle<ref name="meyer">Robert C. Martin: SRP: The Single Responsibility Principle. (PDF) Februar 1997, archiviert vom Vorlage:IconExternal (nicht mehr online verfügbar) am 7. April 2014; abgerufen am 22. April 2014 (Lua-Fehler in Modul:Multilingual, Zeile 153: attempt to index field 'data' (a nil value)).  Info: Der Archivlink wurde automatisch eingesetzt und noch nicht geprüft. Bitte prüfe Original- und Archivlink gemäß Anleitung und entferne dann diesen Hinweis.@1@2Vorlage:Webachiv/IABot/www.objectmentor.com</ref>

Bekannt wurde der Ausdruck durch sein Buch Agile Software Development: Principles, Patterns, and Practices.

In seinem Buch Clean Architecture: A Craftsman’s Guide to Software Structure and Design geht Robert C. Martin auf die Fehlinterpretation des SRP ein und schlägt die „finale Version“ der Definition vor.

“A module should be responsible to one, and only one, actor.”

„Ein Modul sollte einem, und nur einem, Akteur gegenüber verantwortlich sein.“

– <templatestyles src="Person/styles.css" />Robert C. Martin: Clean Architecture: A Craftsman’s Guide to Software Structure and Design

Somit geht es beim SRP nicht nur um die einzelnen Klassen oder Funktionen. Vielmehr geht es um durch die Anforderungen eines Akteurs definierten Sammlungen an Funktionalitäten und Datenstrukturen.

Verallgemeinerung des Single-Responsibility-Prinzips

Funktionen und Variablen

Eine Verallgemeinerung des SRP stellt Curly’s Law dar, welches SRP, methods should do one thing,<ref>Robert C. Martin: Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall International, ISBN 978-0-13-235088-4.</ref> once and only once (OAOO),<ref>Once And Only Once. In: Cunningham & Cunningham. Abgerufen am 26. April 2014 (Lua-Fehler in Modul:Multilingual, Zeile 153: attempt to index field 'data' (a nil value)).</ref> don’t repeat yourself (DRY) und single source of truth (SSOT) zusammenfasst. Das SRP kann und soll demnach für alle Aspekte eines Softwareentwurfs angewendet werden. Dazu gehören nicht nur Klassen, sondern unter anderem auch Funktionen und Variablen. Es ist daher auch bei der Verwendung von nicht-objektorientierten Programmiersprachen und dem Entwurf von Serviceschnittstellen gültig.

“A functional unit on a given level of abstraction should only be responsible for a single aspect of a system’s requirements. An aspect of requirements is a trait or property of requirements, which can change independently of other aspects.”

– <templatestyles src="Person/styles.css" />Ralf Westphal<ref>Ralf Westphal: Taking the Single Responsibility Principle Seriously. In: developerFusion. 6. Februar 2012, abgerufen am 22. April 2014 (Lua-Fehler in Modul:Multilingual, Zeile 153: attempt to index field 'data' (a nil value)).</ref>

“A variable should mean one thing, and one thing only. It should not mean one thing in one circumstance, and carry a different value from a different domain some other time. It should not mean two things at once. It must not be both a floor polish and a dessert topping. It should mean One Thing, and should mean it all of the time.”

– <templatestyles src="Person/styles.css" />Tim Ottinger<ref>Jeff Atwood: Curly’s Law: Do One Thing. In: Coding Horror. 1. März 2007, abgerufen am 22. April 2014 (Lua-Fehler in Modul:Multilingual, Zeile 153: attempt to index field 'data' (a nil value)).</ref>

Beispiel
In dem folgenden Beispiel wird eine Reihe von Zahlen sortiert: <syntaxhighlight lang="csharp"> var numbers = new [] { 5,8,4,3,1 }; numbers = numbers.OrderBy(i => i); </syntaxhighlight>

Da die Variable numbers zuerst die unsortierten Zahlen repräsentiert und nachher die sortierten Zahlen, wird Curly’s Law verletzt. Dies lässt sich auflösen, indem eine zusätzliche Variable eingeführt wird:

<syntaxhighlight lang="csharp"> var numbers = new [] { 5,8,4,3,1 }; var orderedNumbers = numbers.OrderBy(i => i); </syntaxhighlight>

Anwendungen

Auch in der Unix-Philosophie kommt ein ähnliches Prinzip vor, denn hier sollen Anwendungen einen einzelnen Zweck erfüllen.

Make each program do one thing well. By focusing on a single task, a program can eliminate much extraneous code that often results in excess overhead, unnecessary complexity, and a lack of flexibility.

Gestalte jedes Programm so, dass es eine Aufgabe gut erledigt. Durch die Fokussierung auf eine einzelne Aufgabe, kann ein Programm viel unnötigen Code eliminieren, welcher oft zu übertriebenem Overhead, unnötiger Komplexität und mangelnder Flexibilität führt.“

– <templatestyles src="Person/styles.css" />Mike Gancarz: The UNIX Philosophy<ref></ref>

Anwendungen und Benutzerschnittstellen nach einem einzelnen Zweck aufzuteilen, besitzt nicht nur in der Entwicklung Vorteile. Auch für Benutzer sind Programme und Benutzerschnittstellen mit einem klar bestimmten Aufgabenzweck besser verständlich und schneller erlernbar. Nicht zuletzt ergeben sich Vorteile bei beschränkten Bildschirmgrößen, wie dies z. B. bei Smartphones der Fall ist.

Verwandte Muster

Das Interface-Segregation-Prinzip kann als ein Spezialfall des SRP gesehen werden. Es entsteht durch die Anwendung des SRP auf Interfaces.

Command-Query-Separation dient dazu, Funktionen und Entitäten nach ihrer Aufgabe zu trennen, indem zwischen Kommandos ({{Modul:Vorlage:lang}} Modul:Multilingual:153: attempt to index field 'data' (a nil value)) und Abfragen ({{Modul:Vorlage:lang}} Modul:Multilingual:153: attempt to index field 'data' (a nil value)) unterschieden wird. Ähnliches gilt für CQRS, welches unterschiedliche Codepfade für Datenbankzugriffe definiert, welche unabhängig voneinander optimiert werden können.

Querschnittsaspekte

Querschnittsaspekte, welche die gesamte Anwendung betreffen, stellen bezüglich des SRP eine besondere Herausforderung dar. Hierzu zählt insbesondere das Logging.

Bewusster Verstoß gegen das SRP

Viele Entwickler vertreten die Ansicht, dass bei Querschnittsaspekten gegen das SRP verstoßen werden sollte, da Querschnittsaspekte, wie das Logging, so nah wie möglich an der zuständigen Geschäftslogik sein sollten.

<syntaxhighlight lang="csharp"> public sealed class PersonRepository : IPersonRepository {

   private static ILogger Log = ...;
   public Person GetByName(string name)
   {
       try
       {
           return ...;
       }
       catch(Exception ex)
       {
           Log.Error(ex, $"Could not get Person named {name}");
           throw;
       }
   }

} </syntaxhighlight>

Das Logging direkt in der Methode führt allerdings dazu, dass das SRP nicht eingehalten und die Methode spaghettifiziert wird. Das Lesen und Testen der Geschäftslogik wird durch den Code des Aspekts erschwert.

Decorator-Methode

Eine Decorator-Methode ist eine einfache Möglichkeit, den Aspekt und die Geschäftslogik in getrennte Methoden auszulagern.

<syntaxhighlight lang="csharp"> public sealed class PersonRepository : IPersonRepository {

   private static ILogger Log = ...;
   public Person GetByName(string name)
   {
       try
       {
           return GetByNameWithoutLogging(name);
       }
       catch(Exception ex)
       {
           Log.Error(ex, $"Could not get Person named {name}");
           throw;
       }
   }
   private Person GetByNameWithoutLogging(string name)
   {
       return ...;
   }

} </syntaxhighlight>

Nachteilig ist, dass der Aspekt zwar auf Methodenebene ausgelagert wurde, allerdings weiterhin in der Klasse vorhanden ist. Dies stellt daher eine Verletzung des SRP auf Klassenebene dar. Zwar wird die Lesbarkeit verbessert, jedoch stellt sich beim Testen weiterhin die Herausforderung, dass der Aspekt mitgetestet werden muss.

Aspektorientierte Programmierung

Die Aspektorientierte Programmierung (AOP) stellt einen alternativen Ansatz dar, um den Aspekt auszulagern. Hierbei wird die Logik lediglich über eine Auszeichnung definiert und von einem Aspekt-Weaver implementiert.

<syntaxhighlight lang="csharp"> public sealed class PersonRepository : IPersonRepository {

   [LogToErrorOnException]
   public Person GetByName(string name)
   {
       return ...;
   }

} </syntaxhighlight>

Nachteilig ist hierbei, dass das SRP nicht eingehalten wird, da der Aspekt weiterhin in der Klasse verbleibt. Zudem können eventuell nicht alle Aspekte ausgegliedert werden. Beispielsweise kann im obigen Beispiel mit einem Attribut keine parametrisierte Fehlermeldung angegeben werden. Dies führt dazu, dass die Lösung an vielen Stellen annähernd dieselbe Komplexität aufweist wie die ursprüngliche Lösung:

<syntaxhighlight lang="csharp"> public sealed class PersonRepository : IPersonRepository {

   public Person GetByName(string name)
   {
       try
       {
           return ...;
       }
       catch(Exception ex)
       {
           LogTo.Error(ex, $"Could not get Person named {name}");
           throw;
       }
   }

} </syntaxhighlight>

Zudem befindet sich die Logik des Aspekt nach dem Kompiliervorgang weiterhin in der Klasse und erschwert daher weiterhin die Testbarkeit.

Unterklasse

Eine weitere Möglichkeit, den Aspekt von der Geschäftslogik zu trennen, besteht darin, abgeleitete Klassen einzuführen.

<syntaxhighlight lang="csharp"> public class PersonRepository : IPersonRepository {

   public virtual Person GetByName(string name)
   {
       return ...;
   }

}

public sealed class LoggingPersonRepository : PersonRepository {

   private static ILogger Log = ...;
   public override Person GetByName(string name)
   {
       try
       {
           return base.GetByName(name);
       }
       catch(Exception ex)
       {
           Log.Error(ex, $"Could not get Person named {name}");
           throw;
       }
   }

} </syntaxhighlight>

Diese Lösung verstößt allerdings gegen das Prinzip Komposition an Stelle von Vererbung einzusetzen. Unterklassen zur Auslagerung von Aspekten stellen daher ein Antipattern dar.

Decorator

Aspekte lassen sich mittels eines Decorators realisieren und somit von der Geschäftslogik trennen.

<syntaxhighlight lang="csharp"> public sealed class PersonRepository : IPersonRepository {

   public Person GetByName(string name)
   {
       return ...;
   }

}

public sealed class PersonRepositoryLoggingFacade : IPersonRepository {

   private static ILogger Log = ...;
   public IPersonRepository Repository { get; }
   public PersonRepositoryLoggingFacade(PersonRepository repository)
   {
       Repository = repository;
   }
   public Person GetByName(string name)
   {
       try
       {
           return Repository.GetByName(name);
       }
       catch(Exception ex)
       {
           Log.Error(ex, $"Could not get Person named {name}");
           throw;
       }
   }

} </syntaxhighlight>

Der Vorteil hierbei ist, dass das Prinzip der Komposition an Stelle von Vererbung eingehalten wird. Die Klasse PersonRepository kann in Folge gegenüber Vererbung geschlossen werden, da eine Erweiterung durch Komposition jederzeit möglich ist. Ein weiterer Vorteil ist, dass der Aspekt durch eine Konfiguration der Dependency Injection ausgetauscht werden kann. Zudem kann die Logging-Logik unabhängig von der Business-Logik getestet werden.

Nachteilig ist allerdings ein höherer Wartungsaufwand, da in der Dependency Injection sowohl die Klasse mit der Geschäftslogik als auch die Klasse mit dem Aspekt verwaltet werden muss. Durch die Trennung wird zudem die Nachvollziehbarkeit (z. B. in welcher Klasse ein Fehler aufgetreten ist) erschwert.

Raviolicode

Die konsequente Anwendung des Single-Responsibility-Prinzips führt dazu, dass anstatt des Spaghetticodes ein sogenannter Raviolicode entsteht.<ref name="ravioli" /> Dabei handelt es sich um Code mit sehr vielen kleinen Klassen und kleinen Methoden.

Raviolicode besitzt den Nachteil, dass die Menge an Klassen in großen Projekten dazu führt, dass eine geringere Übersichtlichkeit gegeben ist. Dies betrifft insbesondere die in objektorientierten Programmiersprachen auftretenden Functor-Klassen,<ref name="functor" /> also Klassen mit nur einer einzigen Methode.

Das SRP macht somit eine saubere Strukturierung mittels Modulen, Namespaces und Fassaden zwingend notwendig, damit die Übersichtlichkeit nicht verloren geht.

Einzelnachweise

<references> <ref name="ravioli"> Ravioli Code. In: Portland Pattern Repository. 21. Mai 2013, abgerufen am 4. März 2017 (Lua-Fehler in Modul:Multilingual, Zeile 153: attempt to index field 'data' (a nil value)). </ref> <ref name="functor"> Functor Object. In: Portland Pattern Repository. 10. November 2014, abgerufen am 4. März 2017 (Lua-Fehler in Modul:Multilingual, Zeile 153: attempt to index field 'data' (a nil value)). </ref> </references>

<templatestyles src="Erweiterte Navigationsleiste/styles legacy.css" />Vorlage:Klappleiste/Anfang

SOLID-Prinzipien

Single Responsibility • Open Closed • Liskovsches Substitutionsprinzip • Interface Segregation • Dependency Inversion

Weitere Prinzipien

Gesetz von Demeter • Design by Contract • Datenkapselung • Linguistic Modular Units • Self-Documentation • Uniform Access • Single Choice • Persistence Closure • Command-Query-Separation

Packaging-Prinzipien

Reuse Release Equivalence • Common Closure • Common Reuse • Acyclic Dependencies • Stable Dependencies • Stable Abstractions

Vorlage:Klappleiste/Ende