Ich habe eine kleine Debatte mit meinem Freund darüber, ob diese beiden Praktiken nur zwei Seiten derselben Medaille sind oder ob eine wirklich besser ist.
Wir haben eine Funktion, die einen Parameter nimmt, einen Member davon ausfüllt und ihn dann zurückgibt:
Item predictPrice(Item item)
Ich glaube, dass es keinen Sinn macht, das Objekt zurückzugeben , da es mit demselben Objekt funktioniert , das übergeben wird. Aus Sicht des Anrufers ist dies eher verwirrend, da Sie davon ausgehen können, dass ein neues Objekt zurückgegeben wird, das dies nicht tut.
Er behauptet, dass es keinen Unterschied macht, und es wäre nicht einmal wichtig, wenn es einen neuen Gegenstand erschaffen und diesen zurückgeben würde. Ich stimme aus folgenden Gründen überhaupt nicht zu:
Wenn Sie mehrere Verweise auf das Element haben, das übergeben wird (oder Zeiger oder was auch immer), ist es von wesentlicher Bedeutung, ein neues Objekt zuzuweisen und zurückzugeben, da diese Verweise falsch sind.
In nicht speicherverwalteten Sprachen beansprucht die Funktion, die eine neue Instanz zuweist, den Besitz des Speichers, und daher müssten wir eine Bereinigungsmethode implementieren, die irgendwann aufgerufen wird.
Das Zuweisen auf dem Heap ist möglicherweise teuer, und daher ist es wichtig, ob die aufgerufene Funktion dies tut.
Daher halte ich es für sehr wichtig, über eine Methodensignatur erkennen zu können, ob ein Objekt geändert oder ein neues zugewiesen wird. Aus diesem Grund glaube ich, dass die Signatur lauten sollte, da die Funktion lediglich das übergebene Objekt ändert:
void predictPrice(Item item)
In jeder Codebasis (zugegebenermaßen in C- und C ++ - Codebasen, nicht in Java, der Sprache, in der wir arbeiten), mit der ich gearbeitet habe, wurde der obige Stil im Wesentlichen eingehalten und von wesentlich erfahreneren Programmierern eingehalten. Er behauptet, dass meine Stichprobengröße von Codebasen und Kollegen von allen möglichen Codebasen und Kollegen klein ist und daher meine Erfahrung kein wahrer Indikator dafür ist, ob man überlegen ist.
Also irgendwelche Gedanken?
quelle
Item
istclass Item ...
und nichttypedef ...& Item
, ist die lokaleitem
bereits eine KopieAntworten:
Dies ist wirklich eine Ansichtssache, aber für das, was es wert ist, finde ich es irreführend, den geänderten Artikel zurückzugeben, wenn der Artikel an Ort und Stelle geändert wurde. Wenn
predictPrice
das Objekt geändert werden soll, sollte es einen Namen haben, der angibt, dass es dies tun wird (wiesetPredictPrice
oder so).Ich würde es vorziehen (in der Reihenfolge)
Das
predictPrice
war eine Methode vonItem
(modulo ein guter Grund dafür, dass es nicht so ist, wie es in Ihrem Gesamtdesign sein könnte), die auchHat den vorhergesagten Preis zurückgegeben, oder
Hatte
setPredictedPrice
stattdessen einen Namen wieDas
predictPrice
hat nicht geändertItem
, sondern den vorhergesagten Preis zurückgegebenDas
predictPrice
war einevoid
Methode namenssetPredictedPrice
Das Ergebnis wurde
predictPrice
zurückgegebenthis
(für die Verkettung von Methoden mit anderen Methoden, zu welcher Instanz auch immer es gehört) (und wurde aufgerufensetPredictedPrice
).quelle
a = doSomethingWith(b)
geändertb
und mit den Änderungen zurückgegeben, die meinen Code später in die Luft gesprengt haben, weil ich dachte, er wüsste, wasb
passiert ist drin. :-)Innerhalb eines objektorientierten Entwurfsparadigmas sollten Dinge keine Objekte außerhalb des Objekts selbst modifizieren. Alle Änderungen am Status eines Objekts sollten über Methoden für das Objekt vorgenommen werden.
Daher ist die
void predictPrice(Item item)
Funktion eines Mitglieds einer anderen Klasse falsch . Konnte in den Tagen von C akzeptabel sein, aber für Java und C ++ implizieren die Änderungen eines Objekts eine tiefere Kopplung an das Objekt, die wahrscheinlich zu anderen Entwurfsproblemen führen würde (wenn Sie die Klasse umgestalten und ihre Felder ändern, Jetzt muss "predictPrice" in eine andere Datei geändert werden.Zurückgeben eines neuen Objekts, ohne damit verbundene Nebenwirkungen, der übergebene Parameter wird nicht geändert. Sie (die predictPrice-Methode) wissen nicht, wo dieser Parameter sonst verwendet wird. Ist
Item
ein Schlüssel zu einem Hash irgendwo? Haben Sie dabei den Hash-Code geändert? Hält jemand anderes daran fest und erwartet, dass es sich nicht ändert?Diese Entwurfsprobleme deuten nachdrücklich darauf hin, dass Sie Objekte nicht modifizieren sollten (ich würde in vielen Fällen für Unveränderlichkeit eintreten), und wenn Sie dies tun, sollten die Änderungen am Status eher vom Objekt selbst als von etwas anderem enthalten und gesteuert werden sonst außerhalb der Klasse.
Schauen wir uns an, was passiert, wenn man mit den Feldern von etwas in einem Hash spielt. Nehmen wir einen Code:
ideone
Und ich gebe zu, dass dies nicht der größte Code ist (der direkt auf Felder zugreift), aber er dient dazu, ein Problem zu demonstrieren, bei dem veränderbare Daten als Schlüssel für eine HashMap verwendet werden oder in diesem Fall einfach in eine HashSet.
Die Ausgabe dieses Codes ist:
Was passiert ist, ist, dass der Hashcode, der beim Einfügen verwendet wurde, sich dort befindet, wo sich das Objekt im Hash befindet. Durch Ändern der zur Berechnung des Hashcodes verwendeten Werte wird der Hash selbst nicht neu berechnet. Es besteht die Gefahr, dass ein veränderliches Objekt als Schlüssel für einen Hash verwendet wird.
Zurück zum ursprünglichen Aspekt der Frage: Die aufgerufene Methode "weiß" nicht, wie das Objekt, das sie als Parameter erhält, verwendet wird. Wenn Sie die Option "Lässt das Objekt mutieren" als einzige Möglichkeit angeben, kann dies dazu führen, dass sich eine Reihe von subtilen Fehlern eingeschlichen haben. Wie das Verlieren von Werten in einem Hash ... es sei denn, Sie fügen mehr hinzu und der Hash wird erneut aufbereitet - vertrauen Sie mir , das ist ein bösen Fehler auf der Jagd nach (ich den Wert verloren , bis ich 20 weitere Elemente in den Hashmap hinzufügen und dann plötzlich zeigt es wieder).
Verwandte Themen : Überschreiben und Zurückgeben des Werts des Arguments, das als Bedingung für eine if-Anweisung verwendet wird, innerhalb derselben if-Anweisung
quelle
predictPrice
sollte man nicht direkt mitItem
den Mitgliedern herumspielen , aber was ist falsch daran, Methoden dafür aufzurufenItem
? Wenn dies das einzige Objekt ist, das geändert wird, können Sie vielleicht argumentieren, dass es eine Methode für das zu ändernde Objekt sein soll, aber zu anderen Zeiten werden mehrere Objekte (die definitiv nicht derselben Klasse angehören sollten) geändert, insbesondere in eher übergeordnete Methoden.Ich folge immer der Praxis , das Objekt eines anderen nicht zu mutieren . Das heißt, nur mutierende Objekte, die der Klasse / struct / gehören, in der Sie sich befinden.
Ausgehend von der folgenden Datenklasse:
Das ist in Ordnung:
Und das ist ganz klar:
Aber das ist viel zu schlammig und mysteriös für meinen Geschmack:
quelle
Die Syntax unterscheidet sich zwischen verschiedenen Sprachen, und es ist bedeutungslos, einen Codeteil zu zeigen, der nicht für eine Sprache spezifisch ist, da er in verschiedenen Sprachen völlig verschiedene Dinge bedeutet.
In Java
Item
ist es ein Referenztyp - es ist der Typ von Zeigern auf Objekte, von denen Instanzen sindItem
. In C ++ ist dieser Typ wie folgt geschriebenItem *
(die Syntax für dasselbe unterscheidet sich zwischen den Sprachen). AlsoItem predictPrice(Item item)
in Java ist gleichbedeutend mitItem *predictPrice(Item *item)
in C ++. In beiden Fällen ist es eine Funktion (oder Methode), die einen Zeiger auf ein Objekt nimmt und einen Zeiger auf ein Objekt zurückgibt.In C ++ ist
Item
(ohne alles andere) ein Objekttyp (vorausgesetzt, esItem
handelt sich um den Namen einer Klasse). Ein Wert dieses Typs "ist" ein Objekt undItem predictPrice(Item item)
deklariert eine Funktion, die ein Objekt übernimmt und ein Objekt zurückgibt. Da&
es nicht verwendet wird, wird es übergeben und als Wert zurückgegeben. Das heißt, das Objekt wird bei der Übergabe kopiert und bei der Rückgabe kopiert. In Java gibt es keine Entsprechung, da Java keine Objekttypen hat.Also was ist es? Viele der Dinge, die Sie in der Frage stellen (z. B. "Ich glaube, dass es auf demselben Objekt funktioniert, das übergeben wird ..."), hängen davon ab, genau zu verstehen, was hier übergeben und zurückgegeben wird.
quelle
Die Konvention, die ich bei Codebasen gesehen habe, besteht darin, einen Stil zu bevorzugen, der sich aus der Syntax ergibt. Wenn sich der Wert der Funktion ändert, ziehen Sie die Übergabe mit dem Zeiger vor
Dieser Stil führt zu:
Wenn Sie es so nennen, sehen Sie:
predictPrice (& my_item);
und Sie wissen, dass es durch Ihre Einhaltung des Stils geändert wird.
quelle
Obwohl ich keine starke Präferenz habe, würde ich dafür stimmen, dass die Rückgabe des Objekts zu einer besseren Flexibilität für eine API führt.
Beachten Sie, dass es nur Sinn macht, wenn die Methode die Freiheit hat, ein anderes Objekt zurückzugeben. Wenn dein Javadoc sagt,
@returns the same value of parameter a
ist es nutzlos. Wenn Ihr Javadoc jedoch sagt@return an instance that holds the same data than parameter a
, dass dieselbe oder eine andere Instanz zurückgegeben werden soll, handelt es sich um ein Implementierungsdetail.In meinem aktuellen Projekt (Java EE) habe ich beispielsweise eine Business-Schicht. Um einen Mitarbeiter in der Datenbank zu speichern (und eine automatische ID zuzuweisen), sagen wir, einen Mitarbeiter, den ich habe
Natürlich kein Unterschied zu dieser Implementierung, da JPA nach Zuweisung der ID dasselbe Objekt zurückgibt. Nun, wenn ich zu JDBC gewechselt bin
Jetzt bei ????? Ich habe drei Möglichkeiten.
Definieren Sie einen Setter für Id, und ich werde das gleiche Objekt zurückgeben. Hässlich, weil
setId
überall verfügbar sein wird.Machen Sie einige schwere Reflexionsarbeit und setzen Sie die ID in das
Employee
Objekt. Dreckig, aber zumindest ist es auf dencreateEmployee
Rekord beschränkt.Geben Sie einen Konstruktor an, der das Original kopiert
Employee
und auch dasid
zu setzende Objekt akzeptiert .Rufen Sie das Objekt aus der Datenbank ab (möglicherweise erforderlich, wenn einige Feldwerte in der Datenbank berechnet werden oder wenn Sie eine Beziehung auffüllen möchten).
Für 1. und 2. geben Sie möglicherweise dieselbe Instanz zurück, für 3. und 4. geben Sie jedoch neue Instanzen zurück. Wenn Sie in Ihrer API nach dem Prinzip festgelegt haben, dass der "echte" Wert der von der Methode zurückgegebene Wert ist, haben Sie mehr Freiheit.
Das einzige wirkliche Argument, das ich dagegen haben kann, ist, dass bei Verwendung von Implementierungen 1. oder 2. die Benutzer Ihrer API die Zuweisung des Ergebnisses möglicherweise übersehen und ihr Code beschädigt wird, wenn Sie zu Implementierungen 3. oder 4. wechseln.
Wie Sie sehen, basiert meine Präferenz auf anderen persönlichen Präferenzen (z. B. dem Verbot, IDs festzulegen), sodass sie möglicherweise nicht für alle gilt.
quelle
Wenn Sie in Java codieren, sollten Sie versuchen, die Verwendung jedes Objekts mit veränderlichem Typ mit einem von zwei Mustern abzugleichen:
Genau eine Entität wird als "Eigentümer" des veränderlichen Objekts betrachtet und betrachtet den Zustand dieses Objekts als einen Teil seines eigenen. Andere Entitäten können Referenzen besitzen, sollten diese jedoch als Identifikation eines Objekts betrachten, das jemand anderem gehört.
Obwohl das Objekt veränderlich ist, darf niemand, der einen Verweis darauf besitzt, es verändern, wodurch die Instanz effektiv unveränderlich wird (beachten Sie, dass es aufgrund von Macken in Javas Speichermodell keine gute Möglichkeit gibt, ein effektiv unveränderliches Objekt mit Thread zu versehen). sichere unveränderliche Semantik ohne zusätzliche Indirektionsebene für jeden Zugriff). Jede Entität, die einen Zustand unter Verwendung eines Verweises auf ein solches Objekt einkapselt und den eingekapselten Zustand ändern möchte, muss ein neues Objekt erstellen, das den richtigen Zustand enthält.
Ich möchte nicht, dass Methoden das zugrunde liegende Objekt mutieren und eine Referenz zurückgeben, da sie den Anschein erwecken, als würden sie dem zweiten Muster oben entsprechen. Es gibt einige Fälle, in denen der Ansatz hilfreich sein kann, aber Code, der Objekte mutiert, sollte so aussehen , als würde er dies tun. Code like
myThing = myThing.xyz(q);
sieht viel weniger so aus,myThing
als würde er das durch identifizierte Objekt mutieren, als es einfach wäremyThing.xyz(q);
.quelle