Was sind die praktischen Möglichkeiten zur Implementierung des SRP?

11

Welche praktischen Techniken werden verwendet, um zu überprüfen, ob eine Klasse gegen das Prinzip der Einzelverantwortung verstößt?

Ich weiß, dass eine Klasse nur einen Grund haben sollte, sich zu ändern, aber diesem Satz fehlt ein praktischer Weg, dies wirklich umzusetzen.

Der einzige Weg , ich fand , ist , den Satz zu verwenden „Der ......... sollte sich ..........“ Dabei ist das erste Leerzeichen der Klassenname und das spätere der Name der Methode (Verantwortung).

Manchmal ist es jedoch schwierig herauszufinden, ob eine Verantwortung wirklich gegen die SRP verstößt.

Gibt es weitere Möglichkeiten, um nach dem SRP zu suchen?

Hinweis:

Die Frage ist nicht, was die SRP bedeutet, sondern eine praktische Methodik oder eine Reihe von Schritten zur Überprüfung und Implementierung der SRP.

AKTUALISIEREN

Berichtsklasse

Ich habe eine Beispielklasse hinzugefügt, die eindeutig gegen die SRP verstößt. Es wäre großartig, wenn die Menschen anhand dieses Beispiels erklären könnten, wie sie sich dem Prinzip der Einzelverantwortung nähern.

Das Beispiel ist von hier .

Songo
quelle
Dies ist eine interessante Regel, aber Sie könnten trotzdem schreiben: "Eine Personenklasse kann sich selbst rendern". Dies kann als Verstoß für SRP angesehen werden, da es nicht in Ordnung ist, die GUI in dieselbe Klasse aufzunehmen, die Geschäftsregeln und Datenpersistenz enthält. Ich denke, Sie müssen das Konzept der Architekturdomänen (Ebenen und Ebenen) hinzufügen und sicherstellen, dass diese Anweisung nur für eine dieser Domänen gültig ist (z. B. GUI, Datenzugriff usw.)
NoChance
@EmmadKareem Diese Regel wurde in Head First Object-Oriented Analysis and Design erwähnt und genau das habe ich mir darüber gedacht. Es fehlt etwas an einer praktischen Möglichkeit, dies umzusetzen. Sie erwähnten, dass die Verantwortlichkeiten für den Designer manchmal nicht so offensichtlich sind und er mit viel gesundem Menschenverstand beurteilen muss, ob die Methode wirklich in dieser Klasse sein sollte oder nicht.
Songo
Wenn Sie SRP wirklich verstehen wollen, lesen Sie einige Schriften von Onkel Bob Martin. Sein Code ist einer der schönsten, den ich je gesehen habe, und ich vertraue darauf, dass alles, was er über SRP sagt, nicht nur ein guter Rat ist, sondern auch mehr als nur Handbewegung.
Robert Harvey
Und würde der Downwähler bitte erklären, warum der Beitrag verbessert werden soll?!
Songo

Antworten:

7

Die SRP stellt ohne Zweifel fest, dass eine Klasse immer nur einen Grund haben sollte, sich zu ändern.

Bei der Dekonstruktion der Klasse "report" in der Frage gibt es drei Methoden:

  • printReport
  • getReportData
  • formatReport

Wenn man die Redundanz ignoriert Report, die in jeder Methode verwendet wird, ist leicht zu erkennen, warum dies gegen die SRP verstößt:

  • Der Begriff "Drucken" impliziert eine Art Benutzeroberfläche oder einen tatsächlichen Drucker. Diese Klasse enthält daher eine gewisse Menge an Benutzeroberfläche oder Präsentationslogik. Eine Änderung der UI-Anforderungen erfordert eine Änderung der ReportKlasse.

  • Der Begriff "Daten" impliziert eine Datenstruktur, gibt jedoch nicht wirklich an, was (XML? JSON? CSV?). Unabhängig davon, ob sich der "Inhalt" des Berichts jemals ändert, wird sich diese Methode auch ändern. Es besteht entweder eine Kopplung an eine Datenbank oder eine Domäne.

  • formatReportist nur ein schrecklicher Name für eine Methode im Allgemeinen, aber ich würde davon ausgehen, dass sie wieder etwas mit der Benutzeroberfläche zu tun hat und wahrscheinlich einen anderen Aspekt der Benutzeroberfläche als printReport. Also ein weiterer, nicht verwandter Grund, sich zu ändern.

Diese eine Klasse ist also möglicherweise mit einer Datenbank, einem Bildschirm- / Druckergerät und einer internen Formatierungslogik für Protokolle oder Dateiausgaben oder so weiter gekoppelt. Wenn Sie alle drei Funktionen in einer Klasse haben, multiplizieren Sie die Anzahl der Abhängigkeiten und verdreifachen die Wahrscheinlichkeit, dass eine Abhängigkeits- oder Anforderungsänderung diese Klasse (oder etwas anderes, das davon abhängt) zerstört.

Ein Teil des Problems hier ist, dass Sie ein besonders heikles Beispiel ausgewählt haben. Sie sollten wahrscheinlich keine Klasse namens haben Report, auch wenn sie nur eines tut , weil ... welcher Bericht? Sind nicht alle "Berichte" völlig unterschiedliche Bestien, basierend auf unterschiedlichen Daten und unterschiedlichen Anforderungen? Und ist es nicht ein Bericht etwas , das ist bereits formatiert worden ist , entweder für Bildschirm oder für den Druck?

IncomeStatementWenn man jedoch darüber hinausblickt und einen hypothetischen konkreten Namen formuliert - nennen wir es (ein sehr häufiger Bericht) -, hätte eine richtige "SRPed" -Architektur drei Typen:

  • IncomeStatement- die Domänen- und / oder Modellklasse , die die Informationen enthält und / oder berechnet , die in formatierten Berichten angezeigt werden.

  • IncomeStatementPrinter, die wahrscheinlich einige Standardschnittstellen wie implementieren würde IPrintable<T>. Verfügt über eine Schlüsselmethode Print(IncomeStatement)und möglicherweise einige andere Methoden oder Eigenschaften zum Konfigurieren druckspezifischer Einstellungen.

  • IncomeStatementRenderer, das das Rendern von Bildschirmen übernimmt und der Druckerklasse sehr ähnlich ist.

  • Sie können eventuell auch weitere funktionsspezifische Klassen wie IncomeStatementExporter/ hinzufügen IExportable<TReport, TFormat>.

Dies wird in modernen Sprachen durch die Einführung von Generika und IoC-Containern erheblich erleichtert. Der größte Teil Ihres Anwendungscodes muss nicht auf die jeweilige IncomeStatementPrinterKlasse angewiesen sein , sondern kann jede Art von druckbarem Bericht verwenden IPrintable<T>und damit arbeiten. Dadurch erhalten Sie alle wahrgenommenen Vorteile einer Basisklasse mit einer Methode und keiner der üblichen SRP-Verstöße . Die tatsächliche Implementierung muss nur einmal in der IoC-Containerregistrierung deklariert werden.Reportprint

Einige Leute antworten, wenn sie mit dem obigen Design konfrontiert werden, mit etwas wie: "Aber das sieht aus wie prozeduraler Code, und der springende Punkt bei OOP war, uns von der Trennung von Daten und Verhalten fernzuhalten!" Zu dem sage ich: falsch .

Das IncomeStatementsind nicht nur "Daten", und der oben erwähnte Fehler führt dazu, dass viele OOP-Leute das Gefühl haben, dass sie etwas falsch machen, indem sie eine solche "transparente" Klasse erstellen und anschließend alle Arten von nicht verwandten Funktionen in die IncomeStatement( na ja, das, jammen) einbinden und allgemeine Faulheit). Diese Klasse beginnt möglicherweise nur als Daten, wird aber im Laufe der Zeit garantiert eher zu einem Modell .

Eine reale Gewinn- und Verlustrechnung enthält beispielsweise Gesamteinnahmen , Gesamtausgaben und Nettogewinnlinien . Ein ordnungsgemäß gestaltetes Finanzsystem speichert diese höchstwahrscheinlich nicht , da es sich nicht um Transaktionsdaten handelt. Tatsächlich ändern sie sich aufgrund der Hinzufügung neuer Transaktionsdaten. Die Berechnung dieser Zeilen ist jedoch immer exakt gleich, unabhängig davon, ob Sie den Bericht drucken, rendern oder exportieren. So Ihre IncomeStatementKlasse wird eine angemessene Menge von Verhalten , um es in Form haben getTotalRevenues(), getTotalExpenses()und getNetIncome()Methoden, und wahrscheinlich einige andere. Es ist ein echtes Objekt im OOP-Stil mit eigenem Verhalten, auch wenn es nicht wirklich viel zu "tun" scheint.

Aber die formatund printMethoden haben nichts mit den Informationen selbst zu tun. In der Tat ist es nicht allzu unwahrscheinlich, dass Sie mehrere Implementierungen dieser Methoden wünschen , z. B. eine detaillierte Erklärung für das Management und eine nicht so detaillierte Erklärung für die Aktionäre. Durch die Aufteilung dieser unabhängigen Funktionen in verschiedene Klassen können Sie zur Laufzeit verschiedene Implementierungen auswählen, ohne die Last einer einheitlichen print(bool includeDetails, bool includeSubtotals, bool includeTotals, int columnWidth, CompanyLetterhead letterhead, ...)Methode. Yuck!

Hoffentlich können Sie sehen, wo die oben beschriebene, massiv parametrisierte Methode schief geht und wo die einzelnen Implementierungen richtig laufen. Im Fall eines einzelnen Objekts müssen Sie jedes Mal, wenn Sie der Drucklogik eine neue Falte hinzufügen, Ihr Domänenmodell ändern ( Tim in Finance möchte Seitenzahlen, aber nur im internen Bericht, können Sie das hinzufügen? ) Fügen Sie stattdessen einfach einer oder zwei Satellitenklassen eine Konfigurationseigenschaft hinzu.

Bei der ordnungsgemäßen Implementierung des SRP geht es darum, Abhängigkeiten zu verwalten . Kurz gesagt, wenn eine Klasse bereits etwas Nützliches tut und Sie überlegen, eine andere Methode hinzuzufügen, die eine neue Abhängigkeit einführt (z. B. eine Benutzeroberfläche, einen Drucker, ein Netzwerk, eine Datei usw.), tun Sie dies nicht . Überlegen Sie, wie Sie diese Funktionalität stattdessen in eine neue Klasse einfügen und wie Sie diese neue Klasse in Ihre Gesamtarchitektur einfügen können (es ist ziemlich einfach, wenn Sie sich mit Abhängigkeitsinjektion befassen). Das ist das allgemeine Prinzip / der allgemeine Prozess.


Randnotiz: Wie Robert lehne ich offen die Vorstellung ab, dass eine SRP-kompatible Klasse nur eine oder zwei Zustandsvariablen haben sollte. Von solch einer dünnen Hülle konnte selten erwartet werden, dass sie etwas wirklich Nützliches bewirkt. Gehen Sie also nicht über Bord.

Aaronaught
quelle
+1 tolle Antwort in der Tat. Ich bin jedoch nur verwirrt über die Klasse IncomeStatement. Gibt es in Ihrer vorgeschlagene Konstruktion bedeutet , dass die IncomeStatementInstanzen haben IncomeStatementPrinterund IncomeStatementRendererso , dass , wenn ich rufe print()auf IncomeStatementden Anruf wird delegieren IncomeStatementPrinterstatt?
Songo
@ Songo: Auf keinen Fall! Sie sollten keine zyklischen Abhängigkeiten haben, wenn Sie SOLID folgen. Offenbar meine Antwort nicht machen deutlich genug , dass die IncomeStatementKlasse nicht hat eine printMethode oder ein formatVerfahren oder eine andere Methode , die nicht direkt mit Kontrolle oder Manipulation der Berichtsdaten selbst befasst. Dafür sind diese anderen Klassen da. Wenn Sie eine drucken möchten, übernehmen Sie eine Abhängigkeit von der IPrintable<IncomeStatement>Schnittstelle, die im Container registriert ist.
Aaronaught
aah ich verstehe deinen Standpunkt. Wo liegt jedoch die zyklische Abhängigkeit, wenn ich eine PrinterInstanz in die IncomeStatementKlasse einfüge? So wie ich es mir vorstelle, wird IncomeStatement.print()es delegiert, wenn ich es anrufe IncomeStatementPrinter.print(this, format). Was ist falsch an diesem Ansatz? ... Eine weitere Frage, die Sie erwähnt haben und IncomeStatementdie Informationen enthalten sollte, die in formatierten Berichten angezeigt werden, wenn ich möchte, dass sie aus der Datenbank oder aus einer XML-Datei gelesen werden, sollte ich die Methode extrahieren, mit der die Daten geladen werden in eine separate Klasse und delegieren Sie den Anruf an sie in IncomeStatement?
Songo
@Songo: Sie haben IncomeStatementPrinterje nach IncomeStatementund IncomeStatementje nach IncomeStatementPrinter. Das ist eine zyklische Abhängigkeit. Und es ist nur schlechtes Design; Es gibt überhaupt keinen Grund IncomeStatement, etwas über ein Printeroder zu wissen IncomeStatementPrinter- es ist ein Domänenmodell, es befasst sich nicht mit dem Drucken, und die Delegierung ist sinnlos, da jede andere Klasse ein erstellen oder erwerben kann IncomeStatementPrinter. Es gibt keinen guten Grund, im Domain-Modell eine Vorstellung vom Drucken zu haben.
Aaronaught
Wie Sie das IncomeStatementaus der Datenbank (oder XML-Datei) laden - normalerweise wird dies von einem Repository und / oder Mapper verwaltet, nicht von der Domäne, und Sie delegieren dies wiederum nicht in der Domäne. Wenn eine andere Klasse eines dieser Modelle lesen muss, fragt sie explizit nach diesem Repository . Es sei denn, Sie implementieren das Active Record-Muster, aber ich bin wirklich kein Fan.
Aaronaught
2

Ich überprüfe die SRP, indem ich jede Methode (Verantwortung) einer Klasse überprüfe und die folgende Frage stelle:

"Muss ich jemals die Art und Weise ändern, wie ich diese Funktion implementiere?"

Wenn ich eine Funktion finde, die ich auf unterschiedliche Weise implementieren muss (abhängig von einer Konfiguration oder Bedingung), weiß ich mit Sicherheit, dass ich eine zusätzliche Klasse benötige, um diese Verantwortung zu übernehmen.

John Raya
quelle
1

Hier ist ein Zitat aus Regel 8 der Objektkalisthenik :

Die meisten Klassen sollten einfach für die Behandlung einer einzelnen Zustandsvariablen verantwortlich sein, aber es gibt einige, für die zwei erforderlich sind. Das Hinzufügen einer neuen Instanzvariablen zu einer Klasse verringert sofort den Zusammenhalt dieser Klasse. Während der Programmierung nach diesen Regeln gibt es im Allgemeinen zwei Arten von Klassen: diejenigen, die den Status einer einzelnen Instanzvariablen beibehalten, und diejenigen, die zwei separate Variablen koordinieren. Mischen Sie im Allgemeinen nicht die beiden Arten von Verantwortlichkeiten

Angesichts dieser (etwas idealistischen) Ansicht könnte man sagen, dass jede Klasse, die nur eine oder zwei Zustandsvariablen enthält, die SRP wahrscheinlich nicht verletzt. Sie können auch sagen, dass jede Klasse, die mehr als zwei Statusvariablen enthält, die SRP verletzen kann .

MattDavey
quelle
2
Diese Ansicht ist hoffnungslos simpel. Selbst Einsteins berühmte, aber einfache Gleichung erfordert zwei Variablen.
Robert Harvey
Die Frage des OP lautete: "Gibt es mehr Möglichkeiten, nach dem SRP zu suchen?" - Dies ist ein möglicher Indikator. Ja, es ist simpel und hält nicht in jedem Fall stand, aber es ist eine Möglichkeit zu überprüfen, ob gegen SRP verstoßen wurde.
MattDavey
1
Ich vermute, dass ein veränderlicher oder unveränderlicher Zustand ebenfalls eine wichtige Überlegung ist
jk.
Regel 8 beschreibt den perfekten Prozess zum Erstellen von Entwürfen mit Tausenden und Abertausenden von Klassen, wodurch das System hoffnungslos komplex, unverständlich und nicht wartbar wird. Aber das Plus ist, dass Sie SRP folgen können.
Dunk
@Dunk Ich bin nicht anderer Meinung als Sie, aber diese Diskussion ist für die Frage völlig unangebracht.
MattDavey
1

Eine mögliche Implementierung (in Java). Ich habe mir bei den Rückgabetypen Freiheiten genommen, aber insgesamt denke ich, dass dies die Frage beantwortet. TBH Ich denke nicht, dass die Schnittstelle zur Report-Klasse so schlecht ist, obwohl ein besserer Name angebracht sein könnte. Der Kürze halber habe ich Wachaussagen und Behauptungen ausgelassen.

EDIT: Beachten Sie auch, dass die Klasse unveränderlich ist. Sobald es erstellt ist, können Sie nichts mehr ändern. Sie könnten einen setFormatter () und einen setPrinter () hinzufügen und nicht zu viel Ärger bekommen. Der Schlüssel, IMHO, besteht darin, die Rohdaten nach der Instanziierung nicht zu ändern.

public class Report
{
    private ReportData data;
    private ReportDataDao dao;
    private ReportFormatter formatter;
    private ReportPrinter printer;


    /*
     *  Parameterized constructor for depndency injection, 
     *  there are better ways but this is explicit.
     */
    public Report(ReportDataDao dao, 
        ReportFormatter formatter, ReportPrinter printer)
    {
        super();
        this.dao = dao;
        this.formatter = formatter;
        this.printer = printer;
    }

    /*
     * Delegates to the injected printer.
     */
    public void printReport()
    {
        printer.print(formatReport());
    }


    /*
     * Lazy loading of data, delegates to the dao 
     * for the meat of the call.
     */
    public ReportData getReportData()
    {
        if (reportData == null)
        {
            reportData = dao.loadData();
        }
        return reportData;
    }

    /*
     * Delegate to the formatter for formatting 
     * (notice a pattern here).
     */
    public ReportData formatReport()
    {
        formatter.format(getReportData());
    }
}
Heath Lilley
quelle
Danke für die Implementierung. Ich habe 2 Dinge, in der Zeile if (reportData == null), die Sie vermutlich datastattdessen meinen . Zweitens hatte ich gehofft zu wissen, wie Sie zu dieser Implementierung gekommen sind. Zum Beispiel, warum Sie beschlossen haben, alle Aufrufe stattdessen an andere Objekte zu delegieren. Eine weitere Sache, über die ich mich immer gewundert habe: Ist es wirklich die Verantwortung eines Berichts, sich selbst zu drucken?! Warum haben Sie keine separate printerKlasse erstellt, die einen reportKonstruktor enthält?
Songo
Ja, reportData = data, tut mir leid. Die Delegierung ermöglicht eine differenzierte Steuerung von Abhängigkeiten. Zur Laufzeit können Sie für jede Komponente alternative Implementierungen bereitstellen. Jetzt können Sie einen HtmlPrinter, PdfPrinter, JsonPrinter usw. verwenden. Dies ist auch praktisch zum Testen, da Sie Ihre delegierten Komponenten isoliert testen und in das obige Objekt integrieren können. Sie könnten sicherlich die Beziehung zwischen Drucker und Bericht umkehren. Ich wollte nur zeigen, dass es möglich ist, eine Lösung mit der bereitgestellten Klassenschnittstelle bereitzustellen. Es ist eine Gewohnheit, an Legacy-Systemen zu arbeiten. :)
Heath Lilley
hmmmm ... Wenn Sie das System von Grund auf neu erstellen würden, welche Option würden Sie wählen? Eine PrinterKlasse, die einen Bericht oder eine ReportKlasse, die einen Drucker nimmt? Ich bin zuvor auf ein ähnliches Problem gestoßen, bei dem ich einen Bericht analysieren musste, und habe mit meiner TL darüber gestritten, ob wir einen Parser erstellen sollten, der einen Bericht enthält, oder ob der Bericht einen Parser enthalten sollte und der parse()Aufruf an ihn delegiert wird.
Songo
Ich würde beides tun ... Drucker.Druck (Bericht) zum Starten und Bericht.Druck (), falls später benötigt. Das Tolle am Drucker.print (Bericht) -Ansatz ist, dass er in hohem Maße wiederverwendbar ist. Es trennt die Verantwortung und ermöglicht es Ihnen, bequeme Methoden dort einzusetzen, wo Sie sie benötigen. Möglicherweise möchten Sie nicht, dass andere Objekte in Ihrem System etwas über den ReportPrinter wissen müssen. Wenn Sie also eine print () -Methode für eine Klasse verwenden, erreichen Sie eine Abstinenzstufe, die Ihre Berichtsdrucklogik von der Außenwelt isoliert. Dies hat immer noch einen engen Änderungsvektor und ist einfach zu verwenden.
Heath Lilley
0

In Ihrem Beispiel ist nicht klar, ob SRP verletzt wird. Vielleicht sollte der Bericht in der Lage sein, sich selbst zu formatieren und zu drucken, wenn sie relativ einfach sind:

class Report {
  void format() {
     text = text.trim();
  }

  void print() {
     new Printer().write(text);
  }
}

Die Methoden sind so einfach , es keinen Sinn zu haben , macht ReportFormatteroder ReportPrinterKlassen. Das einzige eklatante Problem in der Benutzeroberfläche besteht darin, getReportDatadass es gegen ask not tell für nicht wertvolle Objekte verstößt.

Wenn andererseits die Methoden sehr kompliziert sind oder es viele Möglichkeiten gibt, eine zu formatieren oder zu drucken, Reportist es sinnvoll, die Verantwortung zu delegieren (auch testbarer):

class Report {
  void format(ReportFormatter formatter) {
     text = formatter.format(text);
  }

  void print(ReportPrinter printer) {
     printer.write(text);
  }
}

SRP ist ein Entwurfsprinzip, kein philosophisches Konzept, und basiert daher auf dem tatsächlichen Code, mit dem Sie arbeiten. Semantisch können Sie eine Klasse in beliebig viele Verantwortlichkeiten unterteilen oder gruppieren. Aus praktischen Gründen sollte SRP Ihnen jedoch dabei helfen, den Code zu finden, den Sie ändern müssen . Anzeichen dafür, dass Sie gegen SRP verstoßen, sind:

  • Die Klassen sind so groß, dass Sie Zeit damit verschwenden, zu scrollen oder nach der richtigen Methode zu suchen.
  • Die Klassen sind so klein und zahlreich, dass Sie Zeit damit verschwenden, zwischen ihnen zu springen oder die richtige zu finden.
  • Wenn Sie eine Änderung vornehmen müssen, sind so viele Klassen betroffen, dass es schwierig ist, den Überblick zu behalten.
  • Wenn Sie eine Änderung vornehmen müssen, ist unklar, welche Klassen geändert werden müssen.

Sie können diese Probleme durch Refactoring beheben, indem Sie Namen verbessern, ähnlichen Code gruppieren, Doppelarbeit vermeiden, ein mehrschichtiges Design verwenden und Klassen nach Bedarf aufteilen / kombinieren. Der beste Weg, um SRP zu lernen, besteht darin, in eine Codebasis einzutauchen und den Schmerz zu beseitigen.

Garrett Hall
quelle
Könnten Sie bitte das Beispiel überprüfen, das ich dem Beitrag beigefügt habe, und Ihre Antwort darauf basierend ausarbeiten?
Songo
Aktualisiert. SRP hängt vom Kontext ab. Wenn Sie eine ganze Klasse (in einer separaten Frage) veröffentlicht haben, ist dies einfacher zu erklären.
Garrett Hall
Danke für das Update. Eine Frage: Ist es wirklich die Verantwortung eines Berichts, sich selbst zu drucken?! Warum haben Sie keine separate Druckerklasse erstellt, die einen Bericht in ihrem Konstruktor erstellt?
Songo
Ich sage nur, dass SRP vom Code selbst abhängt, den Sie nicht dogmatisch anwenden sollten.
Garrett Hall
Ja, ich verstehe deinen Standpunkt. Aber wenn Sie das System von Grund auf neu erstellen würden, welche Option würden Sie wählen? Eine PrinterKlasse, die einen Bericht oder eine ReportKlasse, die einen Drucker nimmt? Oft stehe ich vor einer solchen Designfrage, bevor ich herausfinde, ob sich der Code als komplex herausstellt oder nicht.
Songo
0

Das Prinzip der Einzelverantwortung ist eng mit dem Begriff des Zusammenhalts verbunden . Um eine sehr zusammenhängende Klasse zu haben, müssen Sie eine Abhängigkeit zwischen den Instanzvariablen der Klasse und ihren Methoden haben. Das heißt, jede der Methoden sollte so viele Instanzvariablen wie möglich bearbeiten. Je mehr Variablen eine Methode verwendet, desto kohärenter ist ihre Klasse. Ein maximaler Zusammenhalt ist normalerweise nicht erreichbar.

Um SRP gut anzuwenden, verstehen Sie auch die Geschäftslogikdomäne gut. zu wissen, was jede Abstraktion tun sollte. Die geschichtete Architektur hängt auch mit SRP zusammen, indem jede Schicht eine bestimmte Aufgabe ausführt (die Datenquellenschicht sollte Daten bereitstellen usw.).

Zurück zum Zusammenhalt, auch wenn Ihre Methoden nicht alle Variablen verwenden, sollten sie gekoppelt werden:

public class MyClass {
    private Type1 var1;
    private Type2 var2;
    private Type3 var3;

    public Type3 method1() {
        //use var1 and var3
    }  

    public void method2() {
        //use var1 and var2
    }

    public Type1 method3() {
        //use var2 and var3
    }
}

Sie sollten nicht so etwas wie den folgenden Code haben, bei dem ein Teil der Instanzvariablen in einem Teil der Methoden und der andere Teil der Variablen im anderen Teil der Methoden verwendet wird (hier sollten Sie zwei Klassen für haben jeder Teil der Variablen).

public class MyClass {
    private Type1 var1;
    private Type2 var2;
    private Type3 var3;
    private TypeA varA;
    private TypeB varB;

    public Type3 method1() {
        //use var1 and var3
    }  

    public void method2() {
        //use var1 and var2
    }

    public TypeA methodA() {
        //use varA and varB
    }

    public TypeA methodB() {
        //use varA
    }
}
m3th0dman
quelle