Als Referenz - http://en.wikipedia.org/wiki/Single_responsibility_principle
Ich habe ein Testszenario, in dem in einem Anwendungsmodul die Erstellung von Ledger-Einträgen durchgeführt wird. Es gibt drei grundlegende Aufgaben, die ausgeführt werden könnten:
- Zeigen Sie vorhandene Ledger-Einträge im Tabellenformat an.
- Legen Sie über die Schaltfläche Anlegen einen neuen Sachkonteneintrag an.
- Klicken Sie auf einen Ledger-Eintrag in der Tabelle (im ersten Zeiger erwähnt) und sehen Sie sich die Details auf der nächsten Seite an. Sie können einen Ledger-Eintrag auf dieser Seite ungültig machen.
(Auf jeder Seite gibt es noch ein paar Operationen / Validierungen, aber der Kürze halber werde ich es auf diese beschränken.)
Also habe ich beschlossen, drei verschiedene Klassen zu erstellen -
- LedgerLandingPage
- CreateNewLedgerEntryPage
- ViewLedgerEntryPage
Diese Klassen bieten die Dienste an, die auf diesen Seiten ausgeführt werden könnten, und Selentests verwenden diese Klassen, um die Anwendung in einen Zustand zu versetzen, in dem ich bestimmte Behauptungen aufstellen könnte.
Als ich es mit einem meiner Kollegen besprechen ließ, war er überfordert und bat mich, eine einzige Klasse für alle zu machen. Obwohl ich das Gefühl habe, dass mein Design sehr sauber ist, bezweifle ich, dass ich das Prinzip der Einzelverantwortung übertrieben verwende
Antworten:
@YannisRizos hier zitieren :
Ich bin zumindest jetzt nach ungefähr 30 Jahren Programmiererfahrung anderer Meinung. Kleinere Klassen sind
meiner Meinungnachin fast allen Fällen, die mirin Fällen wie diesemeinfallen,besser zu pflegen . Je mehr Funktionen Sie in eine Klasse einordnen, desto weniger Struktur haben Sie und die Qualität Ihres Codes verschlechtert sich - jeden Tag ein bisschen. Wenn ich Tarun richtig verstanden habe, jede dieser 3 OperationenIn einem Anwendungsfall besteht jede dieser Klassen aus mehr als einer Funktion zur Ausführung der zugehörigen Aufgabe, und jede kann separat entwickelt und getestet werden. Das Erstellen von Klassen für jedes von ihnen gruppiert also Dinge, die zusammengehören, was es viel einfacher macht, den zu ändernden Teil des Codes zu identifizieren, wenn sich etwas ändern soll.
Wenn diese drei Klassen gemeinsame Funktionen haben, gehören sie entweder zu einer gemeinsamen Basisklasse, der
Ledger
Klasse selbst (ich nehme an, Sie haben zumindest eine für die CRUD-Operationen) oder zu einer separaten Hilfsklasse.Wenn Sie mehr Argumente benötigen, um viele kleinere Klassen zu erstellen, empfehlen wir Ihnen, sich das Buch "Clean Code" von Robert Martin anzusehen .
quelle
Es gibt keine endgültige Umsetzung des Grundsatzes der einheitlichen Verantwortung oder eines anderen Grundsatzes. Sie sollten entscheiden, was wahrscheinlicher zu ändern ist.
Wie @Karpie in einer früheren Antwort schreibt :
Dies ist ein instinktiver Ansatz, der wahrscheinlich richtig ist, da die Geschäftslogik der Verwaltung von Ledgern mit größerer Wahrscheinlichkeit geändert werden kann. In diesem Fall wäre es viel einfacher, eine Klasse als drei zu unterhalten.
Das sollte aber fallweise geprüft und entschieden werden. Sie sollten sich nicht wirklich um Bedenken mit winzigen Änderungsmöglichkeiten kümmern und sich darauf konzentrieren, das Prinzip darauf anzuwenden, was sich mit größerer Wahrscheinlichkeit ändert. Wenn die Logik der Verwaltung von Ledgern ein mehrschichtiger Prozess ist und Layer dazu neigen, sich unabhängig zu ändern, oder wenn es sich um Bedenken handelt, die grundsätzlich getrennt sein sollten (Logik und Darstellung), sollten Sie separate Klassen verwenden. Es ist ein Spiel der Wahrscheinlichkeit.
Eine ähnliche Frage zum Thema übermäßiger Gebrauch von Gestaltungsprinzipien, die Sie meines Erachtens interessant finden: Gestaltungsmuster: Wann man sie verwendet und wann man aufhört, alles mit Mustern zu tun
quelle
Untersuchen wir diese Klassen:
LedgerLandingPage: Die Rolle dieser Klasse besteht darin, eine Liste von Ledgern anzuzeigen. Wenn die Anwendung wächst, möchten Sie wahrscheinlich Methoden zum Sortieren, Filtern, Hervorheben und transparenten Zusammenführen von Daten aus anderen Datenquellen hinzufügen.
ViewLedgerEntryPage: Diese Seite zeigt das Hauptbuch im Detail. Scheint ziemlich direkt. Solange die Daten einfach sind. Hier können Sie das Ledger aufheben. Sie können es auch korrigieren. Oder kommentieren Sie es oder hängen Sie eine Datei, eine Quittung oder eine externe Buchungsnummer oder was auch immer an. Und wenn Sie die Bearbeitung zulassen, möchten Sie auf jeden Fall einen Verlauf anzeigen.
CreateLedgerEntryPage: Wenn der
ViewLedgerEntryPage
über die volle Bearbeitungsfunktion verfügt, kann die Verantwortung für diese Klasse möglicherweise durch Bearbeiten eines "leeren Eintrags" erfüllt werden, was möglicherweise sinnvoll ist oder nicht.Diese Beispiele scheinen etwas weit hergeholt zu sein, aber der Punkt ist, dass wir hier von einem UX-Feature sprechen. Ein einzelnes Merkmal ist definitiv eine eindeutige, einzelne Verantwortung. Da sich die Anforderungen für diese Funktion ändern können und sich die Anforderungen für zwei verschiedene Funktionen in zwei entgegengesetzte Richtungen ändern können, wünschen Sie sich, Sie wären von Anfang an bei der SRP geblieben.
Umgekehrt können Sie eine Fassade jedoch immer auf drei Klassen aufteilen, wenn eine einzige Benutzeroberfläche aus einem beliebigen Grund bequemer ist.
Es gibt so etwas wie SRP-Überbeanspruchung : Wenn Sie ein Dutzend Klassen benötigen, um den Inhalt einer Datei zu lesen, können Sie davon ausgehen, dass Sie genau das tun.
Wenn Sie die SRP von der anderen Seite betrachten, heißt es eigentlich, dass eine einzelne Klasse für eine einzelne Verantwortung sorgen sollte. Bitte beachten Sie jedoch, dass Verantwortlichkeiten nur innerhalb von Abstraktionsebenen bestehen. Es ist also durchaus sinnvoll, dass eine Klasse einer höheren Abstraktionsebene ihre Verantwortung wahrnimmt, indem sie Arbeit an eine Komponente einer niedrigeren Ebene delegiert, was am besten durch Abhängigkeitsinversion erreicht wird .
quelle
Haben diese Klassen nur einen Grund, sich zu ändern?
Wenn Sie es (noch) nicht wissen, sind Sie möglicherweise in die spekulative Design- / Over-Engineering-Falle geraten (zu viele Klassen, zu komplexe Designmuster angewendet). Sie haben YAGNI nicht zufriedengestellt . In frühen Phasen des Software-Lebenszyklus sind (einige) Anforderungen möglicherweise nicht klar. Daher ist die Richtung der Änderung für Sie derzeit nicht sichtbar. Wenn Sie das bemerken, behalten Sie es in einer oder einer minimalen Anzahl von Klassen. Dies ist jedoch mit einer Haftung verbunden: Sobald die Anforderungen und die Richtung der Änderung klarer sind, müssen Sie das aktuelle Design überdenken und eine Umgestaltung vornehmen, um den Grund der Änderung zu erfüllen.
Wenn Sie bereits wissen, dass es drei verschiedene Gründe für eine Änderung gibt und dass sie diesen drei Klassen zugeordnet sind, haben Sie einfach die richtige SRP-Dosis angewendet. Auch dies ist mit einer gewissen Haftung verbunden: Wenn Sie sich irren oder zukünftige Anforderungen sich unerwartet ändern, müssen Sie das aktuelle Design überdenken und ein Refactoring durchführen, um den Änderungsgrund zu erfüllen.
Der springende Punkt ist:
Wenn Sie diese Einstellung haben, werden Sie den Code ohne große Angst vor spekulativem Design / Over Engineering formen.
Es gibt jedoch immer den Faktor Zeit: Für viele Änderungen haben Sie nicht die Zeit, die Sie benötigen. Refactoring kostet Zeit und Mühe. Ich habe oft Situationen erlebt, in denen ich wusste, dass ich das Design ändern musste, aber aus Zeitgründen musste ich es verschieben. Dies wird passieren und kann nicht vermieden werden. Ich fand, dass es einfacher ist, unter Engineered Code Refactoring durchzuführen, als über Engineered Code. YAGNI gibt mir einen guten Leitfaden: Halten Sie im Zweifelsfall die Anzahl der Klassen und die Anwendung von Entwurfsmustern auf ein Minimum.
quelle
Es gibt drei Gründe, SRP als Grund für das Teilen einer Klasse aufzurufen:
Es gibt drei Gründe, eine Aufteilung einer Klasse abzulehnen:
Wenn ich mir Ihren Fall ansehe und mir Änderungen vorstelle, werden einige von ihnen Änderungen an mehreren Klassen hervorrufen (z. B. ein Feld zum Hauptbuch hinzufügen, alle drei müssen geändert werden), viele jedoch nicht - zum Beispiel das Hinzufügen einer Sortierung zur Liste der Hauptbücher oder Ändern der Validierungsregeln beim Hinzufügen eines neuen Ledger-Eintrags. Die Reaktion Ihres Kollegen ist wahrscheinlich, weil es schwierig ist, herauszufinden, wo Sie nach einem Fehler suchen können. Wenn etwas auf der Liste der Hauptbücher nicht stimmt, liegt es daran, dass es falsch hinzugefügt wurde, oder stimmt etwas mit der Liste nicht? Wenn es eine Diskrepanz zwischen der Zusammenfassung und den Details gibt, was ist falsch?
Es ist auch möglich, dass Ihr Kollege der Ansicht ist, dass die Änderungen der Anforderungen, die bedeuten, dass Sie alle drei Klassen ändern müssen, viel wahrscheinlicher sind als die Änderungen, bei denen Sie nur eine ändern müssen. Das könnte ein wirklich nützliches Gespräch sein.
quelle
Ich denke, wenn das Hauptbuch eine Tabelle in db war, schlage ich vor, diese Thress-Aufgabe einer Klasse zuzuordnen (z. B. DAO). Wenn das Hauptbuch mehr Logik hat und keine Tabelle in db war, schlage ich vor, wir sollten mehr Klassen erstellen, um diese Aufgaben auszuführen (kann zwei oder mehr sein) und bieten eine Fassadenklasse, die einfach für den Kunden zu tun ist.
quelle
IMHO sollten Sie überhaupt keine Klassen verwenden. Hier gibt es keine Datenabstraktionen. Die Ledger sind ein konkreter Datentyp. Die Methoden zur Bearbeitung und Anzeige sind Funktionen und Prozeduren. Es wird sicherlich viele häufig verwendete Funktionen geben, die gemeinsam genutzt werden können, z. B. das Formatieren eines Datums. In den Anzeige- und Auswahlroutinen ist nur wenig Platz, um Daten zu abstrahieren und in einer Klasse zu verbergen.
Es gibt einige Fälle, in denen der Status in einer Klasse für die Ledger-Bearbeitung ausgeblendet werden muss.
Unabhängig davon, ob Sie die SRP missbrauchen oder nicht, missbrauchen Sie etwas Grundlegenderes: Sie überbeanspruchen die Abstraktion. Es ist ein typisches Problem, das durch die mythische Vorstellung hervorgerufen wird, dass OO ein vernünftiges Entwicklungsparadigma ist.
Die Programmierung ist zu 90% konkret: Die Verwendung der Datenabstraktion sollte selten und sorgfältig geplant werden. Funktionale Abstraktion sollte dagegen häufiger vorkommen, da Sprachdetails und die Wahl der Algorithmen von der Semantik der Operation getrennt werden müssen.
quelle
Sie benötigen nur eine Klasse, und die einzige Verantwortung dieser Klasse besteht in der Verwaltung von Ledgern .
quelle