Das Prinzip der Einzelverantwortung ist in Wikipedia definiert als
Das Prinzip der Einzelverantwortung ist ein Computerprogrammierungsprinzip, das besagt, dass jedes Modul, jede Klasse oder Funktion die Verantwortung über einen einzelnen Teil der von der Software bereitgestellten Funktionalität haben sollte und dass die Verantwortung vollständig von der Klasse eingekapselt werden sollte
Wenn eine Klasse nur eine einzige Verantwortung haben soll, wie kann sie dann mehr als eine Methode haben? Hätte nicht jede Methode eine andere Verantwortung, was dann bedeuten würde, dass die Klasse mehr als eine Verantwortung hätte.
Jedes Beispiel, das ich gesehen habe, um das Prinzip der Einzelverantwortung zu demonstrieren, verwendet eine Beispielklasse, die nur eine Methode hat. Es kann hilfreich sein, ein Beispiel zu sehen oder eine Erklärung für eine Klasse mit mehreren Methoden zu haben, von denen immer noch angenommen werden kann, dass sie eine Verantwortung tragen.
Antworten:
Die Einzelverantwortung ist möglicherweise nicht etwas, das eine einzelne Funktion erfüllen kann.
Diese Klasse kann gegen das Prinzip der alleinigen Verantwortung verstoßen. Nicht weil es zwei Funktionen hat, sondern wenn der Code für
getX()
undgetY()
unterschiedliche Stakeholder befriedigen muss, die Änderungen erfordern könnten. Wenn Vizepräsident X ein Memo herumschickt, dass alle Zahlen als Gleitkommazahlen ausgedrückt werden sollen, und die Buchhaltungsdirektorin Y darauf besteht, dass alle Zahlen, die ihre Abteilungsüberprüfungen ergeben, ganze Zahlen bleiben, unabhängig davon, was Herr X für richtig hält, sollte diese Klasse dies besser haben eine einzige Vorstellung davon, wem es verantwortlich ist, weil die Dinge verwirrend werden.Wenn SRP befolgt worden wäre, wäre klar, ob die Standortklasse zu Dingen beiträgt, denen Herr X und seine Gruppe ausgesetzt sind. Stellen Sie klar, wofür die Klasse verantwortlich ist, und Sie wissen, welche Anweisung diese Klasse beeinflusst. Wenn sich beide auf diese Klasse auswirken, wurde sie schlecht konzipiert, um die Auswirkungen von Änderungen zu minimieren. "Eine Klasse sollte nur einen Grund haben, sich zu ändern" bedeutet nicht, dass die gesamte Klasse immer nur eine Kleinigkeit tun kann. Es bedeutet, dass ich nicht in der Lage sein sollte, die Klasse anzusehen und zu sagen, dass sowohl Herr X als auch Frau Y ein Interesse an dieser Klasse haben.
Davon abgesehen. Nein, mehrere Methoden sind in Ordnung. Geben Sie ihm einen Namen, der klar macht, welche Methoden in die Klasse gehören und welche nicht.
In Onkel Bobs SRP geht es mehr um Conways Gesetz als um Curlys Gesetz . Onkel Bob befürwortet die Anwendung von Curlys Gesetz (mache eine Sache) auf Funktionen, nicht auf Klassen. SRP warnt vor Mischungsgründen, die sich gemeinsam ändern. Das Gesetz von Conway besagt, dass das System dem Informationsfluss einer Organisation folgt. Das führt dazu, dass Sie SRP folgen, weil es Ihnen egal ist, wovon Sie nie etwas hören.
Die Leute wollen immer wieder, dass SRP jeder Grund ist, den Umfang einzuschränken. Es gibt mehr Gründe, den Umfang einzuschränken als SRP. Ich schränke den Umfang weiter ein, indem ich darauf bestehe, dass die Klasse eine Abstraktion ist, die einen Namen annehmen kann, der sicherstellt , dass ein Blick ins Innere Sie nicht überrascht .
Sie können Curly's Law auf Klassen anwenden. Sie sind außerhalb dessen, worüber Onkel Bob spricht, aber Sie können es tun. Wenn Sie anfangen zu denken, dass dies eine Funktion bedeutet, können Sie etwas falsch machen. Das ist wie der Gedanke, eine Familie sollte nur ein Kind haben. Mehr als ein Kind zu haben, hindert es nicht daran, eine Familie zu sein.
Wenn Sie Curlys Gesetz auf eine Klasse anwenden, sollte sich alles in der Klasse um eine einzige einheitliche Idee handeln. Diese Idee kann weit gefasst sein. Die Idee könnte Beharrlichkeit sein. Wenn einige Funktionen des Protokollierungsdienstprogramms vorhanden sind, sind sie eindeutig nicht vorhanden. Es spielt keine Rolle, ob Herr X der einzige ist, der sich um diesen Code kümmert.
Das klassische Prinzip, das hier anzuwenden ist, heißt Trennung von Bedenken . Wenn Sie alle Ihre Anliegen trennen, könnte man argumentieren, dass es sich bei dem, was an einem Ort übrig bleibt, um ein Anliegen handelt. So nannten wir diese Idee, bevor uns die City Slickers 1991 mit der Figur Curly bekannt machten.
Das ist okay. Es ist nur so, dass das, was Onkel Bob eine Verantwortung nennt, kein Problem ist. Eine Verantwortung für ihn ist nichts, worauf du dich konzentrierst. Es ist etwas, das dich zwingen kann, dich zu verändern. Sie können sich auf ein Anliegen konzentrieren und dennoch Code erstellen, der für verschiedene Personengruppen mit unterschiedlichen Agenden verantwortlich ist.
Vielleicht interessiert dich das nicht. Fein. Der Gedanke, dass das Halten, um "eine Sache zu tun", all Ihre Designprobleme lösen wird, zeigt einen Mangel an Vorstellungskraft darüber, was "eine Sache" am Ende sein kann. Ein weiterer Grund, den Umfang einzuschränken, ist die Organisation. Sie können viele "eine Sache" in andere "eine Sache" schachteln, bis Sie eine Müllschublade voller Dinge haben. Ich habe darüber gesprochen , bevor
Natürlich besteht der klassische OOP-Grund für die Einschränkung des Gültigkeitsbereichs darin, dass die Klasse private Felder enthält und stattdessen Getter verwendet, um diese Daten gemeinsam zu nutzen. Wir setzen jede Methode, die diese Daten benötigt, in die Klasse ein, in der sie die Daten privat verwenden können. Viele finden dies zu restriktiv, um es als Bereichsbegrenzer zu verwenden, da nicht jede zusammengehörige Methode genau dieselben Felder verwendet. Ich möchte sicherstellen, dass jede Idee, die die Daten zusammengebracht hat, dieselbe Idee ist, die die Methoden zusammengebracht hat.
Die funktionale Art und Weise, dies zu betrachten ist , dass
a.f(x)
unda.g(x)
ist einfach f ein (x) und g a (x). Nicht zwei Funktionen, sondern ein Kontinuum von Funktionspaaren, die sich gemeinsam unterscheiden. Siea
müssen nicht einmal Daten enthalten. Es könnte einfach sein, wie Sie wissen, welchef
und welcheg
Implementierung Sie verwenden werden. Funktionen, die sich gemeinsam ändern, gehören zusammen. Das ist guter alter Polymorphismus.SRP ist nur einer von vielen Gründen, den Umfang einzuschränken. Das ist ein guter. Aber nicht der einzige.
quelle
Der Schlüssel hier ist der Umfang oder, wenn Sie es vorziehen, die Granularität . Ein Teil der durch eine Klasse dargestellten Funktionalität kann weiter in Teile der Funktionalität unterteilt werden, wobei jeder Teil eine Methode ist.
Hier ist ein Beispiel. Stellen Sie sich vor, Sie müssen eine CSV aus einer Sequenz erstellen. Wenn Sie mit RFC 4180 kompatibel sein möchten, würde es einige Zeit dauern, den Algorithmus zu implementieren und alle Edge-Fälle zu behandeln.
Wenn Sie es in einer einzelnen Methode ausführen, wird der Code nicht besonders lesbar sein, und insbesondere führt die Methode mehrere Aktionen gleichzeitig aus. Daher werden Sie es in mehrere Methoden aufteilen; Beispielsweise kann einer von ihnen für die Erzeugung des Headers, dh der allerersten Zeile der CSV, zuständig sein, während eine andere Methode einen Wert eines beliebigen Typs in seine für das CSV-Format geeignete Zeichenfolgendarstellung umwandelt, während eine andere bestimmt, ob a value muss in doppelte Anführungszeichen eingeschlossen werden.
Diese Methoden haben ihre eigene Verantwortung. Die Methode, die prüft, ob Anführungszeichen hinzugefügt werden müssen, hat eine eigene und die Methode, die den Header generiert, eine eigene. Dies ist SRP, das auf Methoden angewendet wird .
Nun haben alle diese Methoden ein Ziel gemeinsam, nämlich eine Sequenz zu erstellen und die CSV zu generieren. Dies liegt in der alleinigen Verantwortung der Klasse .
Pablo H kommentierte:
Tatsächlich. Das von mir angegebene CSV-Beispiel hat idealerweise eine öffentliche Methode, und alle anderen Methoden sind privat. Ein besseres Beispiel wäre eine Warteschlange, die von einer
Queue
Klasse implementiert wird . Diese Klasse würde grundsätzlich zwei Methoden enthalten:push
(auch genanntenqueue
) undpop
(auch genanntdequeue
).Es liegt in der Verantwortung von
Queue.push
, ein Objekt zum Ende der Warteschlange hinzuzufügen.Die Verantwortung von
Queue.pop
besteht darin, ein Objekt aus dem Kopf der Warteschlange zu entfernen und den Fall zu behandeln, in dem die Warteschlange leer ist.Die Verantwortung der
Queue
Klasse besteht darin, eine Warteschlangenlogik bereitzustellen.quelle
Eine Funktion ist eine Funktion.
Eine Verantwortung ist eine Verantwortung.
Ein Mechaniker hat die Verantwortung, Autos zu reparieren, was Diagnose, einige einfache Wartungsaufgaben, einige tatsächliche Reparaturarbeiten, einige Delegation von Aufgaben an andere usw. beinhaltet.
Eine Containerklasse (Liste, Array, Wörterbuch, Karte usw.) ist dafür verantwortlich, Objekte zu speichern. Dazu gehört das Speichern, das Einfügen, das Bereitstellen des Zugriffs, eine Art von Bestellung usw.
Eine einzelne Verantwortung bedeutet nicht, dass es nur sehr wenig Code / Funktionalität gibt, sondern dass alle Funktionen, die vorhanden sind, unter derselben Verantwortung "zusammengehören".
quelle
Einzelverantwortung bedeutet nicht notwendigerweise, dass sie nur eine Sache tut.
Nehmen Sie zum Beispiel eine Benutzerdienstklasse:
Diese Klasse hat mehrere Methoden, aber die Verantwortung ist klar. Es bietet Zugriff auf die Benutzerdatensätze im Datenspeicher. Die einzigen Abhängigkeiten sind das Benutzermodell und der Datenspeicher. Es ist lose gekoppelt und sehr kohäsiv, was SRP wirklich versucht, Sie zum Nachdenken zu bewegen.
SRP sollte nicht mit dem "Prinzip der Schnittstellentrennung" verwechselt werden (siehe SOLID ). Das Prinzip der Schnittstellentrennung (ISP) besagt, dass kleinere, leichtgewichtige Schnittstellen größeren, allgemeineren Schnittstellen vorzuziehen sind. Go nutzt ISP in seiner gesamten Standardbibliothek intensiv:
SRP und ISP sind sicherlich verwandt, aber das eine impliziert nicht das andere. ISP befindet sich auf der Schnittstellenebene und SRP auf der Klassenebene. Wenn eine Klasse mehrere einfache Schnittstellen implementiert, hat sie möglicherweise nicht mehr nur eine Verantwortung.
Vielen Dank an Luaan, der auf den Unterschied zwischen ISP und SRP hingewiesen hat.
quelle
UserService
undUser
werden Uppercamelcase, aber die MethodenCreate
,Exists
undUpdate
ich würde lowerCamelCase gemacht haben.In einem Restaurant gibt es einen Koch. Seine einzige Verantwortung ist das Kochen. Dennoch kann er Steaks, Kartoffeln, Brokkoli und hundert andere Dinge kochen. Würden Sie einen Koch pro Gericht auf Ihrer Speisekarte einstellen? Oder ein Koch für jede Komponente eines Gerichts? Oder ein Koch, der seiner alleinigen Verantwortung gerecht werden kann: Kochen?
Wenn Sie diesen Koch bitten, auch die Gehaltsabrechnung zu übernehmen, verstoßen Sie gegen das SRP.
quelle
Gegenbeispiel: Speicherung des veränderlichen Zustands.
Angenommen, Sie hatten die einfachste Klasse aller Zeiten, deren einzige Aufgabe es ist, eine zu speichern
int
.Wenn Sie auf nur eine Methode beschränkt wären, könnten Sie entweder eine
setState()
oder eine habengetState()
, es sei denn, Sie unterbrechen die Kapselung und machen siei
öffentlich.Es ist klar, dass für diese einzelne Verantwortung mindestens zwei Methoden für diese Klasse erforderlich sind . QED.
quelle
Sie interpretieren das Prinzip der Einzelverantwortung falsch.
Einzelne Verantwortung entspricht nicht einer einzelnen Methode. Sie bedeuten verschiedene Dinge. In der Softwareentwicklung sprechen wir über Zusammenhalt . Funktionen (Methoden), die eine hohe Kohäsion aufweisen, "gehören" zusammen und können als eine Verantwortung ausgeführt werden.
Es ist Sache des Entwicklers, das System so zu gestalten, dass das Prinzip der einmaligen Verantwortung erfüllt wird. Man kann dies als Abstraktionstechnik sehen und ist daher manchmal Ansichtssache. Durch die Implementierung des Grundsatzes der Einzelverantwortung ist der Code hauptsächlich einfacher zu testen und seine Architektur und sein Design besser zu verstehen.
quelle
Es ist oft hilfreich (in jeder Sprache, insbesondere in OO-Sprachen), die Dinge zu betrachten und sie aus der Sicht der Daten und nicht aus der Sicht der Funktionen zu organisieren.
Betrachten Sie die Verantwortung einer Klasse daher als die Aufrechterhaltung der Integrität und als Hilfe bei der korrekten Verwendung der ihr gehörenden Daten. Dies ist natürlich einfacher, wenn sich der gesamte Code in einer Klasse befindet, als auf mehrere Klassen verteilt zu sein. Das Hinzufügen von zwei Punkten ist mit einer
Point add(Point p)
Methode in derPoint
Klasse zuverlässiger und die Pflege des Codes einfacher als mit einer anderen Methode .Insbesondere sollte die Klasse nichts verfügbar machen, was zu inkonsistenten oder falschen Daten führen könnte. Wenn a beispielsweise
Point
in einer Ebene von (0,0) bis (127,127) liegen muss, haben der Konstruktor und alle Methoden, die neuePoint
Werte ändern oder erzeugen , die Verantwortung, die angegebenen Werte zu überprüfen und alle Änderungen abzulehnen, die dies verletzen würden Anforderung. (Oft ist so etwas wie aPoint
unveränderlich, und sicherzustellen, dass es keine Möglichkeiten gibtPoint
, ein Objekt nach seiner Erstellung zu ändern, liegt dann auch in der Verantwortung der Klasse.)Beachten Sie, dass die Schichtung hier vollkommen akzeptabel ist. Möglicherweise haben Sie eine
Point
Klasse für den Umgang mit einzelnen Punkten und einePolygon
Klasse für den Umgang mit einer Menge vonPoint
s. Diese haben immer noch getrennte Verantwortlichkeiten, da diePolygon
Delegierten die gesamte Verantwortung für alles, was ausschließlich mit einem zu tun hatPoint
(z. B. sicherzustellen, dass ein Punkt sowohl einenx
als auch eineny
Wert hat), an diePoint
Klasse delegieren .quelle