Das Prinzip der einheitlichen Verantwortung basiert auf dem Prinzip des hohen Zusammenhalts. Der Unterschied zwischen den beiden besteht darin, dass eine sehr zusammenhängende Klasse eine Reihe von Verantwortlichkeiten aufweist, die eng miteinander verbunden sind, während Klassen, die sich an die SRP halten, nur eine Verantwortung haben.
Aber wie bestimmen wir, ob eine bestimmte Klasse eine Reihe von Verantwortlichkeiten aufweist und daher nur einen hohen Zusammenhalt aufweist oder ob sie nur eine Verantwortung hat und sich somit an die SRP hält? Mit anderen Worten, ist es nicht mehr oder weniger subjektiv, da einige eine Klasse für sehr granular halten (und als solche glauben, dass die Klasse an SRP festhält), während andere sie möglicherweise für nicht granular genug halten?
quelle
Antworten:
Warum ja, es ist sehr subjektiv und es ist das Thema vieler hitziger, rotgesichtiger Debatten, in die Programmierer geraten.
Es gibt nicht wirklich eine Antwort, und die Antwort kann sich ändern, wenn Ihre Software komplexer wird. Was einmal eine einzelne genau definierte Aufgabe war, kann schließlich zu mehreren schlecht definierten Aufgaben werden. Das ist auch immer der Knackpunkt. Wie wählt man den richtigen Weg, um ein Programm in Aufgaben aufzuteilen?
Der einzige Rat, den ich geben kann, ist: Verwenden Sie Ihr (und das Ihrer Kollegen) bestes Urteilsvermögen. Und denken Sie daran, dass Fehler (normalerweise) korrigiert werden können, wenn Sie sie früh genug bemerken.
quelle
Bob Martin (Onkel Bob), der die SOLID- Prinzipien hervorgebracht hat, von denen SRP die erste ist, sagt darüber (ich kann mich umschreiben, kann mich nicht an die tatsächlichen Wörter erinnern):
Wenn es mehrere Gründe gibt, wird SRP nicht eingehalten.
quelle
Ich kann Ihnen einige Faustregeln geben.
quelle
Nach den Grundsätzen der Einzelverantwortung sollte jedes Softwaremodul nur einen Änderungsgrund haben. In einem kürzlich erschienenen Artikel erklärte Onkel Bob "Grund zur Veränderung",
Er erläuterte das Konzept anhand eines Beispiels HIER .
quelle
Um dies zu beantworten, treten Sie einen Schritt zurück und denken Sie über die Absicht des Grundsatzes der einheitlichen Verantwortung nach. Warum ist es überhaupt ein empfohlenes Konstruktionsprinzip?
Der Zweck des Prinzips besteht darin, die Codebasis zu "unterteilen", so dass Code, der sich auf eine einzelne "Verantwortung" bezieht, in einer einzelnen Einheit isoliert ist. Dies erleichtert das Auffinden und Verstehen des Codes, und was noch wichtiger ist, Änderungen an der "Verantwortung" wirken sich nur auf eine einzige Codeeinheit aus.
Was Sie auf keinen Fall in einem System wollen, ist, wenn eine kleine Chance dazu führt, dass ein anderer scheinbar nicht verwandter Teil des Codes versagt oder das Verhalten ändert. Die SRP hilft, Fehler und Änderungen zu isolieren.
Also, was ist es dann eine "Verantwortung"? Es ist etwas, das sich möglicherweise unabhängig von anderen Veränderungen ändern könnte. Angenommen, Sie haben ein Programm, mit dem Sie einige Einstellungen in einer XML-Konfigurationsdatei speichern und aus der Datei wieder einlesen können. Handelt es sich um eine einzelne Verantwortung, oder handelt es sich beim "Laden" und "Speichern" um zwei unterschiedliche Verantwortlichkeiten? Bei jeder Änderung des Dateiformats oder der Dateistruktur müssen sowohl die Lade- als auch die Speicherlogik geändert werden. Es ist daher eine einzelne Verantwortung, die von einer einzelnen Klasse vertreten werden sollte. Betrachten Sie nun eine App, die einige Daten im CVS-, Excel- und XML-Format exportieren kann. In diesem Fall ist es leicht vorstellbar, dass sich ein Format ändern könnte, ohne dass sich dies auf das andere auswirkt. Wenn Sie das Trennzeichen im CVS-Format ändern, sollte dies keine Auswirkungen auf die Excel-Ausgabe haben.
quelle
OO sagt, dass Klassen eine Gruppierung von Daten und Funktionen sind. Diese Definition lässt viel Raum für subjektive Interpretationen.
Wir wissen, dass Klassen klar und einfach definiert werden sollten. Um eine solche Klasse zu definieren, müssen wir jedoch eine klare Vorstellung davon haben, wie eine Klasse in das Gesamtdesign passt. Ohne Anforderungen an den Wasserfalltyp, die paradoxerweise als Antimuster angesehen werden, ist dies schwer zu erreichen.
Wir können ein Klassendesign mit einer Architektur wie MVC implementieren, die in den meisten Fällen funktioniert. In MVC-Anwendungen wird nur vorausgesetzt, dass Daten, eine Benutzeroberfläche und eine Kommunikationsanforderung für beide vorliegen.
Mit einer Basisarchitektur ist es einfacher, Fälle zu identifizieren, in denen Regeln für einzelne Verantwortlichkeiten verletzt werden. EG Übergeben einer Instanz eines Benutzersteuerelements an ein Modal.
quelle
Nur zum Zwecke der Diskussion werde ich eine Klasse von JUCE mit dem Namen AudioSampleBuffer aufrufen . Nun existiert diese Klasse, um ein Snippet (oder vielleicht ein ziemlich langes Snippet) von Audio aufzunehmen. Es ist bekannt, dass die Anzahl der Kanäle und die Anzahl der Samples (pro Kanal) offenbar auf 32-Bit-IEEE-Float festgelegt sind, anstatt eine variable numerische Darstellung oder Wortgröße zu haben (aber das ist für mich kein Problem). Es gibt Member-Funktionen, mit denen Sie die numChannels oder numSamples und Zeiger auf einen bestimmten Kanal abrufen können. Sie können einen AudioSampleBuffer länger oder kürzer machen. Ich gehe davon aus, dass die ersteren den Puffer mit Nullen auffüllen, während die letzteren abschneiden.
Es gibt einige private Member dieser Klasse, die zum Zuweisen von Speicherplatz in dem von JUCE verwendeten speziellen Heap verwendet werden.
Aber das ist, was AudioSampleBuffer fehlt (und ich habe mehrere Diskussionen mit Jules darüber geführt): Ein Mitglied hat angerufen
SampleRate
. Wie konnte es daran fehlen?Die einzige Verantwortung, die ein AudioSampleBuffer erfüllen muss, besteht darin, das physische Audio, das man hört, das seine Samples darstellen, angemessen darzustellen. Wenn Sie einen AudioSampleBuffer aus etwas eingeben, das eine Sounddatei oder einen Stream liest, müssen Sie einen zusätzlichen Parameter abrufen und zusammen mit dem AudioSampleBuffer an Verarbeitungsmethoden übergeben (z. B. einen Filter), die die Samplerate kennen müssen, oder schließlich zu einer Methode, die den Puffer abspielt, um gehört zu werden (oder ihn an einen anderen Ort zu streamen). Wie auch immer.
Sie müssen diese SampleRate, die dem spezifischen Audio im AudioSampleBuffer eigen ist, jedoch weiterhin an alle Stellen weitergeben. Ich habe Code gesehen, in dem eine Konstante 44100.0f an eine Funktion übergeben wurde, weil der Programmierer anscheinend nicht wusste, was er sonst tun sollte.
Dies ist ein Beispiel für die Nichterfüllung seiner alleinigen Verantwortung.
quelle
Auf der Grundlage Ihrer Aussagen kann ein konkreter Weg beschritten werden: Ein hoher Zusammenhalt führt zu einer Verantwortung, an der Sie den Zusammenhalt messen können. Eine maximale zusammenhängende Klasse enthält alle Felder, die in allen Methoden verwendet werden. Während eine maximale Kohäsionsklasse nicht immer möglich oder wünschenswert ist, ist es immer noch am besten, diese zu erreichen. Mit diesem Klassenentwurfsziel lässt sich ziemlich leicht ableiten, dass Ihre Klasse nicht viele Methoden oder Felder haben kann (einige sagen höchstens 7).
Ein anderer Weg ist von den Grundlagen des OOP - Modells nach realen Objekten. Es ist viel einfacher, die Verantwortung der realen Objekte zu erkennen. Wenn das reale Objekt jedoch zu komplex ist, zerlegen Sie es in mehrere zusammenhängende Objekte, von denen jedes seine eigene Verantwortung trägt.
quelle