it-swarm.com.de

Was ist eine "Verantwortung", wenn das Prinzip der Einzelverantwortung angewendet wird?

Es scheint ziemlich klar zu sein, dass "Prinzip der Einzelverantwortung" nicht "nur eine Sache" bedeutet. Dafür sind Methoden da.

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

Bob Martin sagt, dass "Klassen nur einen Grund haben sollten, sich zu ändern." Aber das ist schwierig, wenn Sie ein Programmierer sind, der neu bei SOLID ist.

Ich schrieb eine Antwort auf eine andere Frage , wo ich vorschlug, dass Verantwortlichkeiten wie Berufsbezeichnungen sind, und tanzte um das Thema herum, indem ich eine Restaurantmetapher verwendete, um meinen Standpunkt zu veranschaulichen. Aber das formuliert immer noch keine Reihe von Prinzipien, nach denen jemand die Verantwortlichkeiten seiner Klassen definieren könnte.

Wie machst du das? Wie bestimmen Sie, welche Verantwortlichkeiten jede Klasse haben soll, und wie definieren Sie eine Verantwortung im Kontext von SRP?

201
Robert Harvey

Eine Möglichkeit, sich darüber Gedanken zu machen, besteht darin, sich mögliche Änderungen der Anforderungen in zukünftigen Projekten vorzustellen und sich zu fragen, was Sie tun müssen, um diese umzusetzen.

Zum Beispiel:

Neue Geschäftsanforderungen: Benutzer in Kalifornien erhalten einen Sonderrabatt.

Beispiel für eine "gute" Änderung: Ich muss den Code in einer Klasse ändern, die Rabatte berechnet.

Beispiel für fehlerhafte Änderungen: Ich muss den Code in der Benutzerklasse ändern, und diese Änderung wirkt sich kaskadierend auf andere Klassen aus, die die Benutzerklasse verwenden, einschließlich Klassen, die nichts mit Rabatten zu tun haben, z. Registrierung, Aufzählung und Verwaltung.

Oder:

Neue nicht funktionierende Anforderung: Wir werden Oracle anstelle von SQL Server verwenden

Beispiel für eine gute Änderung: Sie müssen lediglich eine einzelne Klasse in der Datenzugriffsschicht ändern, die bestimmt, wie die Daten in den DTOs beibehalten werden sollen.

Schlechte Änderung: Ich muss alle meine Business-Layer-Klassen ändern, da sie SQL Server-spezifische Logik enthalten.

Die Idee ist, den Platzbedarf zukünftiger potenzieller Änderungen zu minimieren und Codeänderungen auf einen Codebereich pro Änderungsbereich zu beschränken.

Zumindest sollten Ihre Klassen logische Bedenken von physischen Bedenken trennen. Eine große Auswahl an Beispielen finden Sie im System.IO Namespace: Dort finden wir verschiedene Arten von physischen Streams (z. B. FileStream, MemoryStream oder NetworkStream) sowie verschiedene Leser und Schreiber (BinaryWriter) , TextWriter), die auf einer logischen Ebene arbeiten. Indem wir sie auf diese Weise trennen, vermeiden wir eine kombinatorische Explosion: Anstatt FileStreamTextWriter, FileStreamBinaryWriter, NetworkStreamTextWriter, NetworkStreamBinaryWriter, MemoryStreamTextWriter und zu benötigen MemoryStreamBinaryWriter, Sie schließen einfach den Writer und den Stream an und können haben, was Sie wollen. Später können wir beispielsweise ein XmlWriter hinzufügen, ohne es für Speicher, Datei und Netzwerk separat neu implementieren zu müssen.

116
John Wu

In der Praxis sind Verantwortlichkeiten durch die Dinge begrenzt, die sich wahrscheinlich ändern. Daher gibt es keine wissenschaftlichen Erkenntnisse oder ein formelhafter Weg, um leider zu einer Verantwortung zu gelangen. Es ist ein Urteilsspruch.

Es geht darum, was sich Ihrer Erfahrung nach wahrscheinlich ändern wird.

Wir neigen dazu, die Sprache des Prinzips in einer hyperbolischen, wörtlichen, eifrigen Wut anzuwenden. Wir neigen dazu, Klassen zu teilen, weil sie sich ändern können oder in einer Richtung, die uns einfach hilft, Probleme zu lösen. (Der letztere Grund ist nicht von Natur aus schlecht.) Aber die SRP existiert nicht um ihrer selbst willen; Es dient dazu, wartbare Software zu erstellen.

Wenn die Abteilungen nicht durch wahrscheinliche Änderungen gesteuert werden, sind sie nicht wirklich im Dienst der SRP1 wenn YAGNI besser anwendbar ist. Beide dienen demselben Endziel. Und beide sind Urteilssachen - hoffentlich erfahrenes Urteil.

Wenn Onkel Bob darüber schreibt, schlägt er vor, dass wir an "Verantwortung" denken, wenn es darum geht, "wer um die Änderung bittet". Mit anderen Worten, wir wollen nicht, dass Partei A ihren Arbeitsplatz verliert, weil Partei B um eine Änderung gebeten hat.

Wenn Sie ein Softwaremodul schreiben, möchten Sie sicherstellen, dass diese Änderungen, wenn Änderungen angefordert werden, nur von einer einzelnen Person oder vielmehr von einer einzelnen eng gekoppelten Gruppe von Personen stammen können, die eine einzelne eng definierte Geschäftsfunktion repräsentieren. Sie möchten Ihre Module von der Komplexität der gesamten Organisation isolieren und Ihre Systeme so gestalten, dass jedes Modul für die Anforderungen nur dieser einen Geschäftsfunktion verantwortlich ist (auf diese reagiert). ( Onkel Bob - Das Prinzip der Einzelverantwortung )

Gute und erfahrene Entwickler haben ein Gefühl dafür, welche Änderungen wahrscheinlich sind. Und diese mentale Liste wird je nach Branche und Organisation etwas variieren.

Was in Ihrer speziellen Anwendung in Ihrer speziellen Organisation eine Verantwortung darstellt, ist letztendlich eine Frage des erfahrenen Urteils. Es geht darum, was sich wahrscheinlich ändern wird. In gewissem Sinne geht es darum , wem die interne Logik des Moduls gehört .


1. Um klar zu sein, heißt das nicht, dass es sich um schlechte Abteilungen handelt. Dies können großartige Unterteilungen sein, die die Lesbarkeit des Codes dramatisch verbessern. Es bedeutet nur, dass sie nicht von der SRP gesteuert werden.

76
svidgen

Ich folge "Klassen sollten nur einen Grund haben, sich zu ändern".

Für mich bedeutet dies, an hirnrissige Programme zu denken, die mein Produktbesitzer möglicherweise entwickelt hat ("Wir müssen Mobile unterstützen!", "Wir müssen in die Cloud!", "Wir müssen Chinesisch unterstützen!"). Gute Entwürfe beschränken die Auswirkungen dieser Schemata auf kleinere Bereiche und machen sie relativ einfach zu realisieren. Schlechte Designs bedeuten, viel Code zu verwenden und eine Reihe riskanter Änderungen vorzunehmen.

Erfahrung ist das einzige, was ich gefunden habe, um die Wahrscheinlichkeit dieser verrückten Pläne richtig einzuschätzen - denn wenn man es einfach macht, können zwei andere schwieriger werden - und die Güte eines Entwurfs zu bewerten. Erfahrene Programmierer können sich vorstellen, was sie tun müssten, um den Code zu ändern, was herumliegt, um sie in den Arsch zu beißen, und welche Tricks die Dinge einfach machen. Erfahrene Programmierer haben ein gutes Bauchgefühl dafür, wie verrückt sie sind, wenn der Produktbesitzer nach verrückten Sachen fragt.

Praktisch finde ich, dass Unit-Tests hier helfen. Wenn Ihr Code unflexibel ist, wird es schwierig sein, ihn zu testen. Wenn Sie keine Mocks oder andere Testdaten einfügen können, können Sie diesen SupportChinese-Code wahrscheinlich nicht einfügen.

Eine weitere grobe Metrik ist der Höhenunterschied. Traditionelle Aufzugsstellplätze lauten: "Wenn Sie mit einem Investor in einem Aufzug waren, können Sie ihm eine Idee verkaufen?". Startups müssen einfache, kurze Beschreibungen ihrer Aktivitäten haben - worauf sie sich konzentrieren. Ebenso sollten Klassen (und Funktionen) eine einfache Beschreibung dessen haben, was sie tun . Nicht "diese Klasse implementiert einige Fubar, so dass Sie sie in diesen spezifischen Szenarien verwenden können". Etwas, das Sie einem anderen Entwickler sagen können: "Diese Klasse erstellt Benutzer". Wenn Sie dies anderen Entwicklern nicht mitteilen können, werden Sie Fehler bekommen.

29
Telastyn

Niemand weiß. Zumindest können wir uns nicht auf eine Definition einigen. Das macht SPR (und andere SOLID Prinzipien)) ziemlich kontrovers.

Ich würde behaupten, herauszufinden, was eine Verantwortung ist oder nicht, ist eine der Fähigkeiten, die Softwareentwickler im Laufe seiner Karriere erlernen müssen. Je mehr Code Sie schreiben und überprüfen, desto mehr Erfahrung müssen Sie feststellen, ob es sich um eine einzelne oder mehrere Verantwortlichkeiten handelt. Oder wenn die Einzelverantwortung auf verschiedene Teile des Codes verteilt ist.

Ich würde argumentieren, dass der Hauptzweck von SRP nicht darin besteht, eine harte Regel zu sein. Es soll uns daran erinnern, auf den Zusammenhalt im Code zu achten und uns immer bewusst darum zu bemühen, festzustellen, welcher Code zusammenhängend ist und welcher nicht.

26
Euphoric

Ich denke, der Begriff "Verantwortung" ist als Metapher nützlich, weil er es uns ermöglicht, mit der Software zu untersuchen, wie gut die Software organisiert ist. Insbesondere möchte ich mich auf zwei Prinzipien konzentrieren:

  • Die Verantwortung entspricht der Autorität.
  • Keine zwei Einheiten sollten für dasselbe verantwortlich sein.

Diese beiden Prinzipien lassen uns die Verantwortung sinnvoll verteilen, weil sie sich gegenseitig ausspielen. Wenn Sie einen Code befähigen, etwas für Sie zu tun, muss er eine Verantwortung für das haben, was er tut. Dies führt zu der Verantwortung, dass eine Klasse möglicherweise wachsen muss, und erweitert ihren "einen Grund, sich zu ändern" auf immer größere Bereiche. Wenn Sie jedoch die Dinge erweitern, geraten Sie natürlich in Situationen, in denen mehrere Entitäten für dasselbe verantwortlich sind. Dies ist mit Problemen in der Verantwortung im wirklichen Leben behaftet, daher ist es sicherlich auch ein Problem in der Codierung. Infolgedessen werden die Bereiche durch dieses Prinzip enger, da Sie die Verantwortung in nicht duplizierte Pakete unterteilen.

Zusätzlich zu diesen beiden erscheint ein dritter Grundsatz vernünftig:

  • Verantwortung kann delegiert werden

Stellen Sie sich ein frisch geprägtes Programm vor ... eine leere Tafel. Zunächst haben Sie nur eine Entität, nämlich das gesamte Programm. Es ist verantwortlich für ... alles. Natürlich werden Sie irgendwann damit beginnen, die Verantwortung an Funktionen oder Klassen zu delegieren. An diesem Punkt kommen die ersten beiden Regeln ins Spiel, die Sie dazu zwingen, diese Verantwortung auszugleichen. Das Programm der obersten Ebene ist weiterhin für die Gesamtleistung verantwortlich, genau wie ein Manager für die Produktivität seines Teams verantwortlich ist, aber jeder Untereinheit wurde die Verantwortung und damit die Befugnis übertragen, diese Verantwortung wahrzunehmen.

Als zusätzlichen Bonus ist SOLID) besonders kompatibel mit jeder Unternehmenssoftwareentwicklung, die möglicherweise durchgeführt werden muss. Jedes Unternehmen auf dem Planeten hat ein Konzept, wie Verantwortung delegiert werden kann, und nicht alle Stimmen Sie zu. Wenn Sie die Verantwortung innerhalb Ihrer Software auf eine Weise delegieren, die an die Delegation Ihres Unternehmens erinnert, wird es für zukünftige Entwickler viel einfacher sein, sich mit der Vorgehensweise in diesem Unternehmen vertraut zu machen.

5
Cort Ammon

In diese Konferenz in Yale gibt Onkel Bob dieses lustige Beispiel:

(Enter image description here

Er sagt, dass Employee drei Gründe hat, sich zu ändern, drei Quellen für Änderungsanforderungen, und gibt dies humorvoll und ironisch , aber dennoch illustrativ, Erklärung:

  • Wenn die Methode CalcPay() einen Fehler aufweist und das Unternehmen Millionen US-Dollar kostet, wird der CFO Sie entlassen .

  • Wenn die Methode ReportHours() einen Fehler aufweist und das Unternehmen Millionen US $ kostet, wird der COO Sie entlassen .

  • Wenn das WriteEmmployee() Methode hat einen Fehler, der das Löschen vieler Daten verursacht und das Unternehmen Millionen von US $ kostet. Der CTO wird Sie feuern .

Wenn drei verschiedene C-Level-Execs Sie möglicherweise wegen kostspieliger Fehler in derselben Klasse entlassen, bedeutet dies, dass die Klasse zu viele Verantwortlichkeiten hat.

Er gibt diese Lösung an, die die Verletzung von SRP löst, aber dennoch die Verletzung von DIP lösen muss, die im Video nicht gezeigt wird.

(Enter image description here

5

"Prinzip der Einzelverantwortung" ist vielleicht ein verwirrender Name. "Nur ein Grund zur Änderung" ist eine bessere Beschreibung des Prinzips, aber dennoch leicht zu missverstehen. Wir sprechen nicht darüber, was dazu führt, dass Objekte zur Laufzeit ihren Status ändern. Wir überlegen, was dazu führen könnte, dass Entwickler den Code in Zukunft ändern müssen.

Sofern wir keinen Fehler beheben, ist die Änderung auf eine neue oder geänderte Geschäftsanforderung zurückzuführen. Sie müssen außerhalb des Codes selbst denken und sich vorstellen, welche externen Faktoren dazu führen können, dass sich die Anforderungen unabhängig voneinander ändern . Sagen:

  • Die Steuersätze ändern sich aufgrund einer politischen Entscheidung.
  • Das Marketing beschließt, die Namen aller Produkte zu ändern
  • Die Benutzeroberfläche muss neu gestaltet werden, um zugänglich zu sein
  • Die Datenbank ist überlastet, daher müssen Sie einige Optimierungen vornehmen
  • Sie müssen eine mobile App unterbringen
  • und so weiter...

Idealerweise möchten Sie, dass unabhängige Faktoren verschiedene Klassen beeinflussen. Z.B. Da sich die Steuersätze unabhängig von Produktnamen ändern, sollten sich Änderungen nicht auf dieselben Klassen auswirken. Andernfalls besteht die Gefahr, dass bei einer Steueränderung ein Fehler bei der Produktbenennung auftritt. Dies ist die Art der engen Kopplung, die Sie mit einem modularen System vermeiden möchten.

Konzentrieren Sie sich also nicht nur auf das, was sich ändern könnte - irgendetwas könnte sich möglicherweise in Zukunft ändern. Konzentrieren Sie sich darauf, was sich unabhängig ändern könnte. Änderungen sind in der Regel unabhängig, wenn sie von verschiedenen Akteuren verursacht werden.

Ihr Beispiel mit Berufsbezeichnungen ist auf dem richtigen Weg, aber Sie sollten es wörtlicher nehmen! Wenn Marketing Änderungen am Code und Finanzen andere Änderungen verursachen können, sollten diese Änderungen nicht denselben Code betreffen, da es sich buchstäblich um unterschiedliche Berufsbezeichnungen handelt und Änderungen daher unabhängig voneinander vorgenommen werden.

m Onkel Bob zu zitieren der den Begriff erfunden hat:

Wenn Sie ein Softwaremodul schreiben, möchten Sie sicherstellen, dass diese Änderungen, wenn Änderungen angefordert werden, nur von einer einzelnen Person oder vielmehr von einer einzelnen eng gekoppelten Gruppe von Personen stammen können, die eine einzelne eng definierte Geschäftsfunktion repräsentieren. Sie möchten Ihre Module von der Komplexität der gesamten Organisation isolieren und Ihre Systeme so gestalten, dass jedes Modul für die Anforderungen nur dieser einen Geschäftsfunktion verantwortlich ist (auf diese reagiert).

Zusammenfassend lässt sich sagen: Eine "Verantwortung" besteht darin, eine einzelne Geschäftsfunktion zu erfüllen. Wenn mehr als ein Akteur dazu führen kann, dass Sie eine Klasse wechseln müssen, verstößt die Klasse wahrscheinlich gegen dieses Prinzip.

3
JacquesB

Ich denke, eine bessere Möglichkeit, Dinge zu unterteilen als "Gründe für eine Änderung", besteht darin, zunächst zu überlegen, ob es sinnvoll wäre, zu verlangen, dass der Code, der zwei (oder mehr) Aktionen ausführen muss, eine separate Objektreferenz enthalten muss für jede Aktion und ob es nützlich wäre, ein öffentliches Objekt zu haben, das eine Aktion ausführen könnte, die andere jedoch nicht.

Wenn die Antworten auf beide Fragen Ja lauten, würde dies bedeuten, dass die Aktionen von separaten Klassen durchgeführt werden sollten. Wenn die Antworten auf beide Fragen Nein lauten, würde dies bedeuten, dass es aus öffentlicher Sicht eine Klasse geben sollte. Wenn der Code dafür unhandlich wäre, kann er intern in private Klassen unterteilt werden. Wenn die Antwort auf die erste Frage Nein lautet, die zweite Ja, sollte für jede Aktion eine separate Klasse vorhanden sein plus eine zusammengesetzte Klasse, die Verweise auf Instanzen der anderen enthält.

Wenn es separate Klassen für Tastatur, Piepser, numerische Anzeige, Belegdrucker und Kassenschublade einer Registrierkasse und keine zusammengesetzte Klasse für eine vollständige Registrierkasse gibt, wird der Code, der eine Transaktion verarbeiten soll, möglicherweise versehentlich in a aufgerufen Auf diese Weise werden Eingaben über die Tastatur eines Geräts vorgenommen, Geräusche vom Piepser eines zweiten Geräts erzeugt, Zahlen auf dem Display eines dritten Geräts angezeigt, eine Quittung auf dem Drucker eines vierten Geräts gedruckt und die Kassenschublade eines fünften Geräts geöffnet. Jede dieser Unterfunktionen kann sinnvollerweise von einer separaten Klasse verwaltet werden, es sollte jedoch auch eine zusammengesetzte Klasse vorhanden sein, die sie verbindet. Die zusammengesetzte Klasse sollte so viel Logik wie möglich an die konstituierenden Klassen delegieren, sollte jedoch bei praktischen Wrap-Funktionen ihrer konstituierenden Komponenten nicht Client-Code benötigen, um direkt auf die konstituierenden Klassen zuzugreifen.

Man könnte sagen, dass die "Verantwortung" jeder Klasse entweder darin besteht, eine echte Logik zu integrieren oder einen gemeinsamen Anhangspunkt für mehrere andere Klassen bereitzustellen, die dies tun. Wichtig ist jedoch, sich in erster Linie darauf zu konzentrieren, wie Client-Code eine Klasse anzeigen soll. Wenn es für Clientcode sinnvoll ist, etwas als einzelnes Objekt zu sehen, sollte Clientcode es als einzelnes Objekt sehen.

3
supercat

SRP ist schwer richtig zu machen. Es geht hauptsächlich darum, Ihrem Code "Jobs" zuzuweisen und sicherzustellen, dass jedes Teil klare Verantwortlichkeiten hat. Wie im wirklichen Leben kann es in einigen Fällen ganz natürlich sein, die Arbeit unter Menschen aufzuteilen, in anderen Fällen kann es jedoch sehr schwierig sein, insbesondere wenn Sie sie (oder die Arbeit) nicht kennen.

Ich empfehle Ihnen immer nur schreiben Sie einfachen Code, der zuerst funktioniert, und überarbeiten Sie dann ein wenig: Sie werden nach einer Weile sehen, wie sich der Code auf natürliche Weise zusammenballt. Ich denke, es ist ein Fehler, Verantwortlichkeiten zu erzwingen, bevor Sie den Code (oder die Personen) und die zu erledigende Arbeit kennen.

Eine Sache, die Sie bemerken werden, ist, wenn das Modul zu viel zu tun beginnt und schwer zu debuggen/zu warten ist. Dies ist der Moment zum Refactor; Was sollte der Kernjob sein und welche Aufgaben könnten einem anderen Modul übertragen werden? Sollte es beispielsweise Sicherheitsüberprüfungen und andere Arbeiten durchführen oder sollten Sie Sicherheitsüberprüfungen zuerst an anderer Stelle durchführen, oder wird dies den Code komplexer machen?

Verwenden Sie zu viele Indirektionen und es wird wieder ein Chaos ... wie bei anderen Prinzipien wird dieses mit anderen in Konflikt stehen, wie KISS, YAGNI usw. Alles ist eine Frage des Gleichgewichts.

3

Ein guter Artikel, der die Programmierprinzipien SOLID] erklärt und Beispiele für Code enthält, der diesen Prinzipien folgt und nicht folgt, ist https://scotch.io/bar-talk/solid-the -Erste-Fünf-Prinzipien-des-objektorientierten-Designs .

In dem Beispiel zu SRP gibt er ein Beispiel für einige Formklassen (Kreis und Quadrat) und eine Klasse zur Berechnung der Gesamtfläche mehrerer Formen.

In seinem ersten Beispiel erstellt er die Flächenberechnungsklasse und lässt sie seine Ausgabe als HTML zurückgeben. Später beschließt er, es stattdessen als JSON anzuzeigen und muss seine Klasse zur Berechnung des Bereichs ändern.

Das Problem bei diesem Beispiel ist, dass seine Klasse zur Flächenberechnung für die Berechnung der Fläche von Formen UND die Anzeige dieser Fläche verantwortlich ist. Er geht dann einen besseren Weg, um dies mit einer anderen Klasse zu tun, die speziell für die Anzeige von Bereichen entwickelt wurde.

Dies ist ein einfaches Beispiel (und das Lesen des Artikels ist einfacher zu verstehen, da es Codefragmente enthält), zeigt jedoch die Kernidee von SRP.

2
blitz1616

In meinen Augen ist das, was mir am nächsten kommt, ein Nutzungsfluss. Wenn Sie für eine bestimmte Klasse keinen eindeutigen Verwendungsfluss haben, hat Ihre Klasse wahrscheinlich einen Designgeruch.

Ein Nutzungsfluss wäre eine bestimmte Reihenfolge von Methodenaufrufen, die Ihnen ein erwartetes (also überprüfbares) Ergebnis liefert. Grundsätzlich definieren Sie eine Klasse mit den Anwendungsfällen, die sie IMHO erhalten hat. Deshalb konzentrieren sich alle Programmmethoden auf Schnittstellen über die Implementierung.

0
Arthur Havlicek

Um zu erreichen, dass sich mehrere Anforderungen ändern, muss sich Ihre Komponente nicht ändern .

Aber viel Glück beim Verstehen auf den ersten Blick, wenn Sie zum ersten Mal von SOLID hören.


Ich sehe viele Kommentare, die sagen, dass [~ # ~] srp [~ # ~] und [~ # ~] yagni [~ # ~] widersprechen können einander, aber [~ # ~] yagn [~ # ~] Ich habe von TDD (GOOS, London School) brachte mir bei, meine Komponenten aus Kundensicht zu denken und zu entwerfen. Ich begann meine Schnittstellen mit was ist das Mindeste, was ein Client von ihm erwarten würde, so wenig sollte es tun. Und diese Übung kann ohne Kenntnis von TDD durchgeführt werden.

Ich mag die von Onkel Bob beschriebene Technik (ich kann mich leider nicht erinnern, woher), die ungefähr so ​​aussieht:

Fragen Sie sich, was macht diese Klasse?

Enthält Ihre Antwort entweder und oder oder

Wenn ja, extrahieren Sie diesen Teil der Antwort, es liegt in seiner eigenen Verantwortung

Diese Technik ist absolut, und wie @svidgen sagte, ist [~ # ~] srp [~ # ~] ein Urteilsspruch, aber wenn man etwas Neues lernt, sind Absolute das Beste einfacher, einfach immer etwas zu tun. Stellen Sie sicher, dass der Grund, den Sie nicht trennen, ist; eine fundierte Schätzung und nicht weil Sie nicht wissen, wie. Das ist die Kunst, und es braucht Erfahrung.


Ich denke, viele der Antworten scheinen ein Argument für die Entkopplung zu sein, wenn es um [~ # ~] srp [~ # ~] geht.

[~ # ~] srp [~ # ~] is not, um sicherzustellen, dass sich eine Änderung nicht im Abhängigkeitsgraphen ausbreitet.

Theoretisch hätten Sie ohne [~ # ~] srp [~ # ~] keine Abhängigkeiten ...

Eine Änderung sollte nicht an vielen Stellen in der Anwendung zu einer Änderung führen, aber wir haben andere Prinzipien dafür. [~ # ~] srp [~ # ~] verbessert jedoch das Open Closed Principle. Bei diesem Prinzip geht es mehr um Abstraktion. Kleinere Abstraktionen lassen sich jedoch leichter neu implementieren .

Wenn Sie also SOLID als Ganzes unterrichten), achten Sie darauf, dass [~ # ~] srp [~ # ~] Ihnen erlaubt Ändern Sie weniger Code, wenn sich die Anforderungen ändern. Tatsächlich können Sie weniger neuen Code schreiben .

0
Chris Wohlert

Was ich versuche, um Code zu schreiben, der der SRP folgt:

  • Wählen Sie ein bestimmtes Problem aus, das Sie lösen möchten.
  • Schreiben Sie Code, der es löst, schreiben Sie alles in einer Methode (zB: main);
  • Analysieren Sie den Code sorgfältig und versuchen Sie, basierend auf dem Geschäft, die Verantwortlichkeiten zu definieren, die in allen ausgeführten Vorgängen sichtbar sind (dies ist der subjektive Teil, der auch vom Geschäft/Projekt/Kunden abhängt).
  • Bitte beachten Sie, dass alle Funktionen bereits implementiert sind. Was als nächstes kommt, ist nur die Organisation des Codes (in diesem Ansatz werden von nun an keine zusätzlichen Funktionen oder Mechanismen implementiert).
  • Extrahieren Sie basierend auf den Verantwortlichkeiten, die Sie in den vorherigen Schritten definiert haben (die basierend auf dem Unternehmen und der Idee "Ein Grund zur Änderung" definiert wurden), für jede eine eigene Klasse oder Methode.
  • Bitte beachten Sie, dass sich dieser Ansatz nur um die SPR kümmert. Idealerweise sollte es hier zusätzliche Schritte geben, um auch die anderen Prinzipien einzuhalten.

Beispiel :

Problem: Holen Sie sich zwei Zahlen vom Benutzer, berechnen Sie deren Summe und geben Sie das Ergebnis an den Benutzer aus:

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

Versuchen Sie als Nächstes, Verantwortlichkeiten basierend auf Aufgaben zu definieren, die ausgeführt werden müssen. Extrahieren Sie daraus die entsprechenden Klassen:

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

Dann wird das überarbeitete Programm:

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

Hinweis : Dieses sehr einfache Beispiel berücksichtigt nur das SRP-Prinzip. Die Verwendung der anderen Prinzipien (z. B. der "L" -Code sollte eher von Abstraktionen als von Konkretionen abhängen) würde dem Code mehr Vorteile bringen und ihn für geschäftliche Änderungen wartbarer machen.

0
Emerson Cardoso

Darauf gibt es keine klare Antwort. Obwohl die Frage eng ist, sind die Erklärungen nicht.

Für mich ist es so etwas wie Occams Rasiermesser, wenn Sie wollen. Es ist ein Ideal, an dem ich versuche, meinen aktuellen Code zu messen. Es ist schwer, es in einfachen Worten festzunageln. Eine andere Metapher wäre "ein Thema", das so abstrakt, d. H. Schwer zu erfassen ist, wie "Einzelverantwortung". Eine dritte Beschreibung wäre "Umgang mit einer Abstraktionsebene".

Was bedeutet das praktisch?

In letzter Zeit verwende ich einen Codierungsstil, der hauptsächlich aus zwei Phasen besteht:

Phase I lässt sich am besten als kreatives Chaos beschreiben. In dieser Phase schreibe ich Code auf, während Gedanken fließen - d. H. Roh und hässlich.

Phase II ist das genaue Gegenteil. Es ist wie nach einem Hurrikan aufzuräumen. Dies erfordert die meiste Arbeit und Disziplin. Und dann betrachte ich den Code aus der Sicht eines Designers.

Ich arbeite jetzt hauptsächlich in Python, wodurch ich später an Objekte und Klassen denken kann. Erste Phase I - Ich schreibe nur Funktionen und verteile sie fast zufällig in verschiedenen Modulen. In Phase II schaue ich mir, nachdem ich die Dinge in Gang gebracht habe, genauer an, welches Modul sich mit welchem ​​Teil der Lösung befasst. Und während ich durch die Module blättere, tauchen für mich Themen auf. Einige Funktionen sind thematisch miteinander verbunden. Dies sind gute Kandidaten für Klassen . Und nachdem ich Funktionen in Klassen umgewandelt habe - was fast mit Einrücken und Hinzufügen von self zur Parameterliste in python;) erledigt ist - verwende ich SRP wie Occams Rasiermesser zum Entfernen Funktionalität auf andere Module und Klassen übertragen.

Ein aktuelles Beispiel könnte sein, dass neulich kleine Exportfunktionen geschrieben werden.

Es bestand Bedarf an csv , Excel und kombiniert Excel-Tabellen in einer Zip.

Die einfache Funktionalität wurde jeweils in drei Ansichten (= Funktionen) ausgeführt. Jede Funktion verwendete eine gemeinsame Methode zum Bestimmen von Filtern und eine zweite Methode zum Abrufen der Daten. Dann fand in jeder Funktion die Vorbereitung des Exports statt und wurde als Antwort vom Server geliefert.

Es wurden zu viele Abstraktionsebenen verwechselt:

I) Umgang mit eingehenden/ausgehenden Anfragen/Antworten

II) Bestimmen von Filtern

III) Abrufen von Daten

IV) Transformation von Daten

Der einfache Schritt bestand darin, eine Abstraktion (exporter) zu verwenden, um in einem ersten Schritt die Schichten II-IV zu behandeln.

Das einzige verbleibende war das Thema , das sich mit Anfragen/Antworten befasste . Auf derselben Abstraktionsebene werden Anforderungsparameter extrahiert, was in Ordnung ist. Also hatte ich für diese Ansicht eine "Verantwortung".

Zweitens musste ich den Exporteur auflösen, der, wie wir sahen, aus mindestens drei weiteren Abstraktionsebenen bestand.

Die Bestimmung der Filterkriterien und des tatsächlichen Retrivals erfolgt nahezu auf derselben Abstraktionsebene (Die Filter werden benötigt, um die richtige Teilmenge der Daten zu erhalten). Diese Ebenen wurden in eine Art Datenzugriffsschicht eingefügt.

Im nächsten Schritt habe ich die eigentlichen Exportmechanismen auseinandergebrochen: Wo das Schreiben in eine temporäre Datei erforderlich war, habe ich dies in zwei "Verantwortlichkeiten" unterteilt: eine für das eigentliche Schreiben der Daten auf die Festplatte und einen anderen Teil, der sich mit dem tatsächlichen Format befasste.

Mit der Bildung der Klassen und Module wurde klarer, was wohin gehörte. Und immer die latente Frage, ob die Klasse zu viel tut .

Wie bestimmen Sie, welche Verantwortlichkeiten jede Klasse haben soll, und wie definieren Sie eine Verantwortung im Kontext von SRP?

Es ist schwer, ein Rezept zu geben, dem man folgen kann. Natürlich könnte ich die kryptische "eine Abstraktionsebene" -Regel wiederholen, wenn das hilft.

Meistens ist es für mich eine Art "künstlerische Intuition", die zum aktuellen Design führt; Ich modelliere Code wie ein Künstler Ton formen oder malen kann.

Stellen Sie sich mich als Coding Bob Ross vor ;)

0
Thomas Junk

Aus dem Buch Robert C. Martins Saubere Architektur: Ein Leitfaden für Handwerker zu Softwarestruktur und -design , veröffentlicht am 10. September 2017, schreibt Robert auf Seite 62 Folgendes:

In der Vergangenheit wurde die SRP folgendermaßen beschrieben:

Ein Modul sollte einen und nur einen Grund zur Änderung haben

Softwaresysteme werden geändert, um Benutzer und Stakeholder zufrieden zu stellen. Diese Benutzer und Stakeholder sind der "Grund zur Änderung". dass das Prinzip spricht. In der Tat können wir das Prinzip umformulieren, um Folgendes zu sagen:

Ein Modul sollte nur einem Benutzer oder Stakeholder gegenüber verantwortlich sein

Leider sind das Wort "Benutzer" und "Stakeholder" nicht wirklich das richtige Wort, um es hier zu verwenden. Es wird wahrscheinlich mehr als einen Benutzer oder Stakeholder geben, der möchte, dass das System auf vernünftige Weise geändert wird. Stattdessen beziehen wir uns wirklich auf eine Gruppe - eine oder mehrere Personen, die diese Änderung benötigen. Wir werden diese Gruppe als Schauspieler bezeichnen.

Somit ist die endgültige Version des SRP:

Ein Modul sollte einem und nur einem Schauspieler verantwortlich sein.

Es geht also nicht um Code. Bei der SRP geht es darum, den Fluss von Anforderungen und Geschäftsanforderungen zu steuern, die nur von einer Quelle stammen können.

0
Benny Skogberg

Zuallererst haben Sie tatsächlich zwei separate Probleme: das Problem, welche Methoden in Ihre Klassen eingefügt werden sollen, und das Problem des Aufblähens der Benutzeroberfläche.

Schnittstellen

Sie haben diese Schnittstelle:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Vermutlich haben Sie mehrere Klassen, die der CustomerCRUD -Schnittstelle entsprechen (andernfalls ist eine Schnittstelle nicht erforderlich), und einige Funktionen do_crud(customer: CustomerCRUD), die ein konformes Objekt aufnehmen. Aber Sie haben die SRP bereits gebrochen: Sie haben diese vier verschiedenen Operationen miteinander verbunden.

Nehmen wir an, Sie würden später Datenbankansichten bearbeiten. Für eine Datenbankansicht steht nur die Methode Read zur Verfügung. Sie möchten jedoch eine Funktion do_query_stuff(customer: ???) schreiben, die Operatoren transparent entweder für vollständige Tabellen oder Ansichten ausführt. es wird schließlich nur die Methode Read verwendet.

Erstellen Sie also eine Schnittstelle

öffentliche Schnittstelle CustomerReader {public Customer Read (customerID: int)}

und faktorisieren Sie Ihre CustomerCrud Schnittstelle als:

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Aber ein Ende ist nicht in Sicht. Es könnte Objekte geben, die wir erstellen, aber nicht aktualisieren können usw. Dieses Kaninchenloch ist zu tief. Der einzig vernünftige Weg, sich an das Prinzip der Einzelverantwortung zu halten, besteht darin, alle Ihre Schnittstellen enthalten genau eine Methode. Go folgt dieser Methodik tatsächlich aus dem, was ich gesehen habe, wobei die überwiegende Mehrheit der Schnittstellen eine einzige Funktion enthält. Wenn Sie eine Schnittstelle angeben möchten, die zwei Funktionen enthält, müssen Sie umständlich eine neue Schnittstelle erstellen, die beide kombiniert. Sie erhalten bald eine kombinatorische Explosion von Schnittstellen.

Der Ausweg aus diesem Durcheinander besteht darin, strukturelle Subtypisierung (implementiert in z. B. OCaml) anstelle von Schnittstellen (die eine Form der nominalen Subtypisierung sind) zu verwenden. Wir definieren keine Schnittstellen. Stattdessen können wir einfach eine Funktion schreiben

let do_customer_stuff customer = customer.read ... customer.update ...

das nennt jede Methode, die wir mögen. OCaml verwendet die Typinferenz, um zu bestimmen, dass wir jedes Objekt übergeben können, das diese Methoden implementiert. In diesem Beispiel wird festgestellt, dass customer den Typ <read: int -> unit, update: int -> unit, ...> Hat.

Klassen

Dies löst das Schnittstelle Chaos; Wir müssen jedoch noch Klassen implementieren, die mehrere Methoden enthalten. Sollten wir zum Beispiel zwei verschiedene Klassen erstellen, CustomerReader und CustomerWriter? Was ist, wenn wir ändern möchten, wie Tabellen gelesen werden (z. B. werden unsere Antworten jetzt vor dem Abrufen der Daten in Redis zwischengespeichert), aber jetzt, wie sie geschrieben werden? Wenn Sie this Argumentationskette zu ihrer logischen Schlussfolgerung folgen, werden Sie untrennbar zur funktionalen Programmierung geführt :)

0
gardenhead