it-swarm.com.de

Verstößt das Hinzufügen eines Rückgabetyps zu einer Aktualisierungsmethode gegen das "Prinzip der Einzelverantwortung"?

Ich habe eine Methode, die Mitarbeiterdaten in der Datenbank aktualisiert. Die Klasse Employee ist unveränderlich. Das "Aktualisieren" des Objekts bedeutet also, ein neues Objekt tatsächlich zu instanziieren.

Ich möchte, dass die Methode Update eine neue Instanz von Employee mit den aktualisierten Daten zurückgibt, aber seitdem kann ich sagen, dass die Verantwortung für die Methode ist, um die Mitarbeiterdaten zu aktualisieren, und holen aus der Datenbank ein neues Employee Objekt, verstößt es gegen das Single Responsibility Principle =?

Der DB-Datensatz selbst wird aktualisiert. Dann wird ein neues Objekt instanziiert, um diesen Datensatz darzustellen.

37
Sipo

Wie bei jeder Regel denke ich, dass es hier wichtig ist, den Zweck der Regel, den Geist, zu berücksichtigen und nicht genau zu analysieren, wie die Regel in einem Lehrbuch formuliert wurde und wie man sie auf diesen Fall anwendet. Wir müssen uns dem nicht wie Anwälte nähern. Der Zweck der Regeln ist es, uns zu helfen, bessere Programme zu schreiben. Es ist nicht so, dass der Zweck des Schreibens von Programmen darin besteht, die Regeln einzuhalten.

Der Zweck der Einzelverantwortungsregel besteht darin, das Verständnis und die Wartung von Programmen zu vereinfachen, indem jede Funktion eine eigenständige, kohärente Aufgabe erfüllt.

Zum Beispiel habe ich einmal eine Funktion geschrieben, die ich so etwas wie "checkOrderStatus" genannt habe, die feststellte, ob eine Bestellung aussteht, versendet, nachbestellt wird, was auch immer, und einen Code zurückgegeben hat, der angibt, welche. Dann kam ein anderer Programmierer und modifizierte diese Funktion, um auch die Menge zu aktualisieren, die beim Versand der Bestellung verfügbar war. Dies verstieß schwer gegen das Prinzip der Einzelverantwortung. Ein anderer Programmierer, der diesen Code später liest, sieht den Funktionsnamen, sieht, wie der Rückgabewert verwendet wurde, und vermutet möglicherweise nie, dass eine Datenbankaktualisierung durchgeführt wurde. Jemand, der den Bestellstatus erhalten musste, ohne die verfügbare Menge zu aktualisieren, wäre in einer schwierigen Lage: Sollte er eine neue Funktion schreiben, die den Bestellstatus-Teil dupliziert? Fügen Sie ein Flag hinzu, um anzugeben, ob das Datenbank-Update durchgeführt werden soll. Usw. (Natürlich wäre die richtige Antwort, die Funktion in zwei Teile zu teilen, aber das ist aus vielen Gründen möglicherweise nicht praktikabel.)

Andererseits würde ich nicht herausfinden, was "zwei Dinge" ausmacht. Ich habe kürzlich eine Funktion geschrieben, die Kundeninformationen von unserem System an das System unseres Kunden sendet. Diese Funktion führt eine Neuformatierung der Daten durch, um deren Anforderungen zu erfüllen. Zum Beispiel haben wir einige Felder in unserer Datenbank, die möglicherweise null sind, aber keine Nullen zulassen, sodass wir einen Dummy-Text "nicht angegeben" eingeben müssen, oder ich vergesse die genauen Wörter. Wahrscheinlich macht diese Funktion zwei Dinge: die Daten neu formatieren UND senden. Aber ich habe dies ganz bewusst in eine einzige Funktion eingefügt, anstatt "neu formatieren" und "senden" zu haben, weil ich niemals ohne Neuformatierung senden möchte. Ich möchte nicht, dass jemand einen neuen Anruf schreibt und nicht merkt, dass er neu formatieren und dann senden muss.

In Ihrem Fall aktualisieren Sie die Datenbank und geben Sie ein Bild des geschriebenen Datensatzes zurück. Dies scheint zwei Dinge zu sein, die logisch und unvermeidlich zusammenpassen könnten. Ich kenne die Details Ihrer Bewerbung nicht, daher kann ich nicht definitiv sagen, ob dies eine gute Idee ist oder nicht, aber es klingt plausibel.

Wenn Sie ein Objekt im Speicher erstellen, das alle Daten für den Datensatz enthält, die Datenbankaufrufe ausführen, um dies zu schreiben, und dann das Objekt zurückgeben, ist dies sehr sinnvoll. Sie haben das Objekt in Ihren Händen. Warum nicht einfach zurückgeben? Wenn Sie das Objekt nicht zurückgeben würden, wie würde der Anrufer es erhalten? Müsste er die Datenbank lesen, um das Objekt zu erhalten, das Sie gerade geschrieben haben? Das scheint eher ineffizient. Wie würde er die Aufzeichnung finden? Kennen Sie den Primärschlüssel? Wenn jemand erklärt, dass es für die Schreibfunktion "legal" ist, den Primärschlüssel zurückzugeben, damit Sie den Datensatz erneut lesen können, warum nicht einfach den gesamten Datensatz zurückgeben, damit Sie dies nicht tun müssen? Was ist der Unterschied?

Wenn das Erstellen des Objekts jedoch eine Menge Arbeit ist, die sich vom Schreiben des Datenbankeintrags deutlich unterscheidet, und ein Aufrufer möglicherweise das Schreiben ausführen möchte, das Objekt jedoch nicht erstellt, kann dies verschwenderisch sein. Wenn ein Aufrufer das Objekt möglicherweise möchte, aber nicht schreibt, müssen Sie eine andere Möglichkeit angeben, um das Objekt abzurufen. Dies kann das Schreiben von redundantem Code bedeuten.

Aber ich denke, Szenario 1 ist wahrscheinlicher, also würde ich sagen, wahrscheinlich kein Problem.

16
Jay

Das Konzept des SRP besteht darin, zu verhindern, dass Module zwei verschiedene Aufgaben ausführen, wenn sie einzeln ausgeführt werden. Dies sorgt für eine bessere Wartung und weniger Zeit für Spaghetti. Wie die SRP sagt "ein Grund zur Änderung".

Wenn Sie in Ihrem Fall eine Routine hatten, die "das aktualisierte Objekt aktualisiert und zurückgibt", ändern Sie das Objekt immer noch einmal - und geben ihm einen Grund für eine Änderung. Dass Sie das Objekt sofort zurückgeben, ist weder hier noch da. Sie arbeiten immer noch an diesem einzelnen Objekt. Der Code ist für eine und nur eine Sache verantwortlich.

Beim SRP geht es nicht wirklich darum, alle Vorgänge auf einen einzigen Anruf zu reduzieren, sondern darum, was Sie bearbeiten und wie Sie damit arbeiten. Ein einzelnes Update, das das aktualisierte Objekt zurückgibt, ist also in Ordnung.

67
gbjbaanb

Wie immer ist dies eine Frage des Grades. Das SRP sollte Sie daran hindern, eine Methode zu schreiben, mit der ein Datensatz aus einer externen Datenbank abgerufen, eine schnelle Fourier-Transformation durchgeführt und eine globale Statistikregistrierung mit dem Ergebnis aktualisiert wird. Ich denke, fast jeder würde zustimmen, dass diese Dinge mit unterschiedlichen Methoden erledigt werden sollten. Das Postulieren einer einzelnen Verantwortung für jede Methode ist einfach der wirtschaftlichste und einprägsamste Weg, um diesen Punkt zu verdeutlichen.

Am anderen Ende des Spektrums befinden sich Methoden, die Informationen über den Zustand eines Objekts liefern. Das typische isActive hat diese Informationen als seine einzige Verantwortung. Wahrscheinlich sind sich alle einig, dass dies in Ordnung ist.

Einige erweitern das Prinzip nun so weit, dass sie die Rückgabe einer Erfolgsflagge als eine andere Verantwortung betrachten als die Durchführung der Aktion, deren Erfolg gemeldet wird. Unter einer extrem strengen Interpretation ist dies wahr, aber da die Alternative darin besteht, eine zweite Methode aufzurufen, um den Erfolgsstatus zu erhalten, was den Aufrufer kompliziert, sind viele Programmierer vollkommen in der Lage, Erfolgscodes von einer Methode mit Nebenwirkungen zurückzugeben.

Die Rückgabe des neuen Objekts ist ein Schritt weiter auf dem Weg zu mehreren Verantwortlichkeiten. Das Auffordern des Anrufers, einen zweiten Anruf für ein gesamtes Objekt zu tätigen, ist etwas sinnvoller als das Erfordernis eines zweiten Anrufs, um festzustellen, ob der erste erfolgreich war oder nicht. Dennoch würden viele Programmierer in Betracht ziehen, das Ergebnis eines Updates einwandfrei zurückzugeben. Während dies als zwei leicht unterschiedliche Verantwortlichkeiten ausgelegt werden kann, ist es sicherlich nicht einer der ungeheuren Missbräuche, die das Prinzip von Anfang an inspiriert haben.

46
Kilian Foth

Verstößt es gegen das Prinzip der Einzelverantwortung?

Nicht unbedingt. Wenn überhaupt, verstößt dies gegen das Prinzip von Befehlsabfrage Trennung.

Die Verantwortung liegt bei

aktualisieren Sie die Mitarbeiterdaten

mit dem impliziten Verständnis, dass diese Verantwortung den Status der Operation umfasst; Wenn der Vorgang beispielsweise fehlschlägt, wird eine Ausnahme ausgelöst. Wenn dies erfolgreich ist, wird der Aktualisierungsmitarbeiter zurückgegeben.

Auch dies ist alles eine Frage des Grades und des subjektiven Urteils.


Was ist also mit der Trennung von Befehl und Abfrage?

Nun, dieses Prinzip existiert, aber die Rückgabe des Ergebnisses eines Updates ist in der Tat sehr verbreitet.

(1) Java Set#add(E) fügt das Element hinzu und gibt seine vorherige Aufnahme in die Menge zurück.

if (visited.add(nodeToVisit)) {
    // perform operation once
}

Dies ist effizienter als die CQS-Alternative, bei der möglicherweise zwei Suchvorgänge durchgeführt werden müssen.

if (!visited.contains(nodeToVisit)) {
    visited.add(nodeToVisit);
    // perform operation once
}

(2) Compare-and-Swap , Fetch-and-Add und Test-and-Set sind gängige Grundelemente, die eine gleichzeitige Programmierung ermöglichen existieren. Diese Muster treten häufig auf, von CPU-Anweisungen auf niedriger Ebene bis hin zu gleichzeitigen Sammlungen auf hoher Ebene.

8
Paul Draper

Die einzige Verantwortung betrifft eine Klasse, die sich aus mehr als einem Grund nicht ändern muss.

Als Beispiel hat ein Mitarbeiter eine Liste von Telefonnummern. Wenn sich die Art und Weise, wie Sie mit Telefonnummern umgehen, ändert (Sie können Länderanrufcodes hinzufügen), sollte dies die Mitarbeiterklasse überhaupt nicht ändern.

Ich hätte nicht die Notwendigkeit, dass die Mitarbeiterklasse wissen muss, wie sie sich in der Datenbank speichert, da sie sich dann für Änderungen an Mitarbeitern und Änderungen an der Speicherung von Daten ändern würde.

Ebenso sollte es in der Mitarbeiterklasse keine CalculateSalary-Methode geben. Eine Gehaltsimmobilie ist in Ordnung, aber die Berechnung der Steuern usw. sollte woanders erfolgen.

Es ist jedoch in Ordnung, dass die Update-Methode das zurückgibt, was sie gerade aktualisiert hat.

2
Bent

Wrt. der spezielle Fall:

Employee Update(Employee, name, password, etc) (verwendet tatsächlich einen Builder, da ich viele Parameter habe).

Es scheint , dass die Update -Methode ein vorhandenes Employee als ersten Parameter verwendet, um den vorhandenen Mitarbeiter und a zu identifizieren (?) Satz von Parametern, die für diesen Mitarbeiter geändert werden sollen.

Ich do denke, dass dies sauberer gemacht werden könnte . Ich sehe zwei Fälle:

(a)Employee enthält tatsächlich eine Datenbank/eindeutige ID, anhand derer sie immer in der Datenbank identifiziert werden kann. (Das heißt, Sie benötigen nicht die gesamten Datensatzwerte, um sie in der Datenbank zu finden.

In diesem Fall würde ich eine void Update(Employee obj) -Methode bevorzugen, die nur den vorhandenen Datensatz anhand der ID findet und dann die Felder des übergebenen Objekts aktualisiert. Oder vielleicht eine void Update(ID, EmployeeBuilder values)

Eine Variation davon, die ich nützlich fand, besteht darin, nur eine void Put(Employee obj) -Methode zu haben, die einfügt oder aktualisiert, je nachdem, ob der Datensatz (nach ID) vorhanden ist.

(b) Der vollständige vorhandene Datensatz wird für die DB-Suche benötigt. In diesem Fall ist es möglicherweise noch sinnvoller, Folgendes zu haben: void Update(Employee existing, Employee newData).

Soweit ich hier sehen kann, würde ich in der Tat sagen, dass die Verantwortung für das Erstellen eines neuen Objekts (oder von Unterobjektwerten) zum Speichern und tatsächlichen Speichern orthogonal ist, sodass ich sie trennen würde.

Die in anderen Antworten genannten gleichzeitigen Anforderungen (atomares Set-and-Retrieve/Compare-Swap usw.) waren in dem DB-Code, an dem ich bisher gearbeitet habe, kein Problem. Wenn Sie mit einer Datenbank sprechen, sollten Sie dies normalerweise auf Transaktionsebene und nicht auf der Ebene der einzelnen Anweisungen tun. (Das heißt nicht, dass es möglicherweise kein Design gibt, bei dem "atomar" Employee[existing data] Update(ID, Employee newData) keinen Sinn ergibt, aber mit DB-Zugriffen ist dies normalerweise nicht der Fall.)

2
Martin Ba

Bisher sprechen alle hier über Klassen. Aber denken Sie aus einer Schnittstellenperspektive darüber nach.

Wenn eine Schnittstellenmethode einen Rückgabetyp und/oder ein implizites Versprechen zum Rückgabewert deklariert, muss jede Implementierung das aktualisierte Objekt zurückgeben.

Sie können also fragen:

  • Können Sie sich mögliche Implementierungen vorstellen, bei denen Sie kein neues Objekt zurückgeben möchten?
  • Können Sie sich Komponenten vorstellen, die von der Schnittstelle abhängen (indem eine Instanz injiziert wird), für die der aktualisierte Mitarbeiter nicht als Rückgabewert benötigt wird?

Denken Sie auch an Scheinobjekte für Unit-Tests. Offensichtlich wird es einfacher sein, sich ohne den Rückgabewert zu verspotten. Und abhängige Komponenten sind einfacher zu testen, wenn ihre injizierten Abhängigkeiten einfachere Schnittstellen haben.

Basierend auf diesen Überlegungen können Sie eine Entscheidung treffen.

Und wenn Sie es später bereuen, können Sie immer noch eine zweite Schnittstelle mit Adaptern zwischen den beiden einführen. Natürlich erhöhen solche zusätzlichen Schnittstellen die Komplexität der Komposition, so dass alles ein Kompromiss ist.

1
donquixote

Ihr Ansatz ist in Ordnung. Unveränderlichkeit ist eine starke Behauptung. Ich würde nur fragen: Gibt es einen anderen Ort, an dem Sie das Objekt konstruieren? Wenn Ihr Objekt nicht unveränderlich wäre, müssten Sie zusätzliche Fragen beantworten, da "State" eingeführt wird. Die Zustandsänderung eines Objekts kann aus verschiedenen Gründen erfolgen. Dann sollten Sie Ihre Fälle kennen und sie sollten nicht überflüssig oder geteilt sein.

0
oopexpert