it-swarm.com.de

Mutable vs unveränderliche Objekte

Ich versuche, den Kopf zwischen veränderlichen und unveränderlichen Objekten zu bewegen. Die Verwendung von veränderlichen Objekten verursacht eine Menge Druckfehler (z. B. das Zurückgeben einer Reihe von Zeichenfolgen aus einer Methode), aber ich habe Probleme zu verstehen, welche negativen Auswirkungen dies hat. Was sind die Best Practices für die Verwendung veränderlicher Objekte? Sollten Sie sie nach Möglichkeit meiden?

162
Alex Angas

Nun, das hat ein paar Aspekte. Nummer eins, veränderbare Objekte ohne Referenzidentität können gelegentlich Fehler verursachen. Betrachten Sie beispielsweise eine Person-Bean mit einer auf Werten basierenden equals-Methode:

Map<Person, String> map = ...
Person p = new Person();
map.put(p, "Hey, there!");

p.setName("Daniel");
map.get(p);       // => null

Die Instanz Person geht in der Map "verloren", wenn sie als Schlüssel verwendet wird, da es sich um hashCode handelt und die Gleichheit auf veränderlichen Werten basiert. Diese Werte haben sich außerhalb der Karte geändert und das gesamte Hashing ist veraltet. Theoretiker mögen es, in diesem Punkt zu harfen, aber in der Praxis habe ich es nicht als zu großes Problem empfunden.

Ein weiterer Aspekt ist die logische "Plausibilität" Ihres Codes. Dies ist ein schwer zu definierender Begriff, der alles von der Lesbarkeit bis zum Fluss umfasst. Im Allgemeinen sollten Sie in der Lage sein, sich einen Code anzusehen und leicht zu verstehen, was er tut. Wichtiger ist jedoch, dass Sie sich selbst davon überzeugen können, dass es das tut, was es tut . . Wenn sich Objekte unabhängig voneinander in verschiedenen Code- "Domänen" ändern können, ist es manchmal schwierig, den Überblick darüber zu behalten, wo und warum sie sich befinden ("unheimliche Aktion in einiger Entfernung"). Dies ist ein schwieriger zu veranschaulichendes Konzept, das jedoch häufig in größeren, komplexeren Architekturen zu finden ist.

Schließlich sind veränderbare Objekte in gleichzeitigen Situationen Killer . Immer wenn Sie von separaten Threads aus auf ein veränderbares Objekt zugreifen, müssen Sie sich mit dem Sperren befassen. Dies verringert den Durchsatz und erschwert die Wartung Ihres Codes erheblich . Ein ausreichend kompliziertes System löst dieses Problem so weit aus, dass es selbst für Parallelitätsexperten kaum mehr zu warten ist.

Unveränderliche Objekte (und insbesondere unveränderliche Sammlungen) vermeiden all diese Probleme. Sobald Sie sich ein Bild davon gemacht haben, wie sie funktionieren, wird sich Ihr Code zu etwas entwickeln, das leichter zu lesen, leichter zu warten und seltener auf seltsame und unvorhersehbare Weise versagt. Unveränderliche Objekte sind noch einfacher zu testen, nicht nur aufgrund ihrer einfachen Verspottbarkeit, sondern auch aufgrund der Codemuster, die sie tendenziell erzwingen. Kurz gesagt, sie sind rundum eine gute Übung!

Trotzdem bin ich in dieser Angelegenheit kaum ein Eiferer. Einige Probleme lassen sich nicht gut modellieren, wenn alles unveränderlich ist. Aber ich denke, dass Sie versuchen sollten, so viel Code wie möglich in diese Richtung zu pushen, vorausgesetzt natürlich, Sie verwenden eine Sprache, die dies zu einer haltbaren Meinung macht (C/C++ macht dies sehr schwierig, ebenso wie Java). . Kurz gesagt: Die Vorteile hängen etwas von Ihrem Problem ab, aber ich würde Unveränderlichkeit vorziehen.

155
Daniel Spiewak

Unveränderliche Objekte vs. Unveränderliche Sammlungen

Einer der Feinpunkte in der Debatte um veränderbare und unveränderliche Objekte ist die Möglichkeit, das Konzept der Unveränderlichkeit auf Sammlungen auszudehnen. Ein unveränderliches Objekt ist ein Objekt, das häufig eine einzelne logische Datenstruktur darstellt (z. B. eine unveränderliche Zeichenfolge). Wenn Sie einen Verweis auf ein unveränderliches Objekt haben, ändert sich der Inhalt des Objekts nicht.

Eine unveränderliche Sammlung ist eine Sammlung, die sich nie ändert.

Wenn ich eine Operation für eine veränderbare Sammlung durchführe, ändere ich die Sammlung an Ort und Stelle, und alle Entitäten, die Verweise auf die Sammlung haben, werden die Änderung sehen.

Wenn ich eine Operation an einer unveränderlichen Sammlung durchführe, wird ein Verweis auf eine neue Sammlung zurückgegeben, der die Änderung widerspiegelt. Alle Entitäten, die Verweise auf frühere Versionen der Sammlung haben, sehen die Änderung nicht.

Clevere Implementierungen müssen nicht unbedingt die gesamte Sammlung kopieren (klonen), um diese Unveränderlichkeit zu gewährleisten. Das einfachste Beispiel ist der als einfach verknüpfte Liste implementierte Stapel und die Push/Pop-Operationen. Sie können alle Knoten aus der vorherigen Sammlung in der neuen Sammlung wiederverwenden, indem Sie nur einen einzigen Knoten für den Push hinzufügen und keine Knoten für den Pop klonen. Die Push_tail-Operation für eine einzeln verknüpfte Liste ist andererseits nicht so einfach oder effizient.

Unveränderliche vs. veränderbare Variablen/Referenzen

Bei einigen funktionalen Sprachen wird das Konzept der Unveränderlichkeit auf Objektreferenzen selbst angewendet, sodass nur eine einzige Referenzzuweisung möglich ist.

  • In Erlang gilt dies für alle "Variablen". Ich kann einer Referenz nur einmal Objekte zuordnen. Wenn ich eine Sammlung bearbeiten würde, könnte ich die neue Sammlung nicht der alten Referenz (Variablenname) zuordnen.
  • Scala baut dies auch in die Sprache ein, indem alle Referenzen mit var oder val deklariert werden, wobei vals nur eine einzelne Zuweisung ist und einen funktionalen Stil fördert, aber vars eine zulässt mehr C-ähnliche oder Java-ähnliche Programmstruktur.
  • Die var/val-Deklaration ist erforderlich, während viele traditionelle Sprachen optionale Modifikatoren wie final in Java und const in verwenden C.

Einfache Entwicklung vs. Leistung

Fast immer besteht der Grund für die Verwendung eines unveränderlichen Objekts darin, nebenwirkungsfreies Programmieren und einfaches Argumentieren für den Code zu fördern (insbesondere in einer stark parallelen Umgebung). Sie müssen sich keine Sorgen machen, dass die zugrunde liegenden Daten von einer anderen Entität geändert werden, wenn das Objekt unveränderlich ist.

Der Hauptnachteil ist die Leistung. Hier ist eine Zusammenfassung von ein einfacher Test, den ich in Java durchgeführt habe Vergleichen einiger unveränderlicher und veränderlicher Objekte in einem Spielzeugproblem.

Die Leistungsprobleme treten in vielen Anwendungen auf, aber nicht in allen. Aus diesem Grund ermöglichen viele große numerische Pakete, wie die Numpy Array-Klasse in Python, direkte Aktualisierungen großer Arrays. Dies wäre wichtig für Anwendungsbereiche, die große Matrix- und Vektoroperationen verwenden. Diese großen datenparallelen und rechenintensiven Probleme erzielen eine große Beschleunigung, wenn sie vor Ort ausgeführt werden.

25
Ben Jackson

Überprüfen Sie diesen Blog-Beitrag: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Es erklärt, warum unveränderliche Objekte besser als veränderlich sind. Zusamenfassend:

  • unveränderliche Objekte sind einfacher zu konstruieren, zu testen und zu verwenden
  • wirklich unveränderliche Objekte sind immer threadsicher
  • sie tragen dazu bei, eine zeitliche Kopplung zu vermeiden
  • ihre Verwendung ist frei von Nebenwirkungen (keine defensiven Kopien)
  • identitätsveränderlichkeitsproblem wird vermieden
  • sie haben immer Versagen Atomizität
  • sie sind viel einfacher zwischenzuspeichern
11
yegor256

Unveränderliche Objekte sind ein sehr mächtiges Konzept. Sie entlasten den Versuch, Objekte/Variablen für alle Clients konsistent zu halten.

Sie können sie für nicht polymorphe Objekte auf niedriger Ebene verwenden - wie z. B. eine CPoint-Klasse -, die hauptsächlich mit Wertesemantik verwendet werden.

Oder Sie können sie für polymorphe Interfaces auf hoher Ebene verwenden - wie eine IFunction, die eine mathematische Funktion darstellt -, die ausschließlich mit der Objektsemantik verwendet wird.

Größter Vorteil: Unveränderlichkeit + Objektsemantik + intelligente Zeiger machen den Besitz von Objekten zum Kinderspiel. Alle Clients des Objekts verfügen standardmäßig über eine eigene private Kopie. Implizit bedeutet dies auch deterministisches Verhalten bei gleichzeitiger Anwesenheit.

Nachteil: Bei Verwendung von Objekten mit vielen Daten kann der Speicherverbrauch zu einem Problem werden. Eine Lösung hierfür könnte darin bestehen, Operationen an einem Objekt symbolisch zu halten und eine verzögerte Auswertung durchzuführen. Dies kann jedoch zu symbolischen Berechnungsketten führen, die sich negativ auf die Leistung auswirken können, wenn die Schnittstelle nicht für symbolische Operationen ausgelegt ist. In diesem Fall sollten Sie unbedingt vermeiden, dass von einer Methode große Speicherbereiche zurückgegeben werden. In Kombination mit verketteten symbolischen Operationen kann dies zu massivem Speicherverbrauch und Leistungseinbußen führen.

Unveränderliche Objekte sind definitiv meine primäre Denkweise in Bezug auf objektorientiertes Design, aber sie sind kein Dogma. Sie lösen eine Menge Probleme für Kunden von Objekten, schaffen aber auch viele, insbesondere für die Implementierer.

10
QBziZ

Sie sollten angeben, über welche Sprache Sie sprechen. Für Low-Level-Sprachen wie C oder C++ bevorzuge ich die Verwendung veränderlicher Objekte, um Speicherplatz zu sparen und die Speicherabwanderung zu reduzieren. In höheren Sprachen erleichtern unveränderliche Objekte das Erkennen des Verhaltens des Codes (insbesondere Multithread-Code), da in einiger Entfernung keine "gruselige Aktion" stattfindet.

6
John Millikin

Ein veränderbares Objekt ist einfach ein Objekt, das geändert werden kann, nachdem es erstellt/instanziiert wurde, im Vergleich zu einem unveränderlichen Objekt, das nicht geändert werden kann (siehe die Wikipedia-Seite zum Thema). Ein Beispiel hierfür in einer Programmiersprache sind Pythons-Listen und -Tupel. Listen können geändert werden (z. B. können neue Elemente hinzugefügt werden, nachdem sie erstellt wurden), während Tupel dies nicht können.

Ich glaube nicht wirklich, dass es eine eindeutige Antwort gibt, welche für alle Situationen besser ist. Sie haben beide ihre Plätze.

4
willurd

Wenn ein Klassentyp änderbar ist, kann eine Variable dieses Klassentyps verschiedene Bedeutungen haben. Angenommen, ein Objekt foo hat ein Feld int[] arr Und enthält einen Verweis auf ein int[3] Mit den Zahlen {5, 7, 9}. Obwohl der Typ des Feldes bekannt ist, kann es mindestens vier verschiedene Dinge darstellen:

  • Eine potenziell gemeinsam genutzte Referenz, deren Inhaber nur darauf achten, dass die Werte 5, 7 und 9 gekapselt werden. Wenn foo unterschiedliche Werte in arr einschließen soll, muss sie durch ein anderes Array ersetzt werden, das die gewünschten Werte enthält. Wenn Sie eine Kopie von foo erstellen möchten, können Sie der Kopie entweder einen Verweis auf arr oder ein neues Array mit den Werten {1,2,3} geben, je nachdem, was bequemer ist.

  • Der einzige Verweis irgendwo im Universum auf ein Array, das die Werte 5, 7 und 9 kapselt. Satz von drei Speicherorten, die im Moment die Werte 5, 7 und 9 enthalten; Wenn foo die Werte 5, 8 und 9 einkapseln soll, kann es entweder das zweite Element in diesem Array ändern oder ein neues Array mit den Werten 5, 8 und 9 erstellen und das alte verlassen. Wenn Sie eine Kopie von foo erstellen möchten, müssen Sie in der Kopie arr durch einen Verweis auf ein neues Array ersetzen, damit foo.arr Als einziger Verweis auf dieses Array im gesamten Universum erhalten bleibt.

  • Ein Verweis auf ein Array, das einem anderen Objekt gehört, das es aus irgendeinem Grund für foo verfügbar gemacht hat (z. B. möchte es, dass foo dort einige Daten speichert). In diesem Szenario kapselt arr nicht den Inhalt des Arrays, sondern sein Identität. Da das Ersetzen von arr durch einen Verweis auf ein neues Array seine Bedeutung vollständig ändern würde, sollte eine Kopie von foo einen Verweis auf dasselbe Array enthalten.

  • Ein Verweis auf ein Array, dessen einziger Eigentümer foo ist, dessen Verweise jedoch aus irgendeinem Grund von einem anderen Objekt gespeichert werden (z. B. möchte es, dass das andere Objekt dort Daten speichert - die Kehrseite des vorherigen Falls). In diesem Szenario kapselt arr sowohl die Identität des Arrays als auch dessen Inhalt. Das Ersetzen von arr durch einen Verweis auf ein neues Array würde seine Bedeutung völlig ändern, aber das Verweisen von arr auf foo.arr Eines Klons würde die Annahme verletzen, dass foo der alleinige Eigentümer ist. foo kann daher nicht kopiert werden.

Theoretisch sollte int[] Ein einfacher, klar definierter Typ sein, der jedoch vier sehr unterschiedliche Bedeutungen hat. Im Gegensatz dazu hat ein Verweis auf ein unveränderliches Objekt (z. B. String) im Allgemeinen nur eine Bedeutung. Ein Großteil der "Kraft" unveränderlicher Objekte beruht auf dieser Tatsache.

1
supercat

Unveränderliche Mittel können nicht geändert werden, und veränderliche Mittel können geändert werden.

Objekte unterscheiden sich von Grundelementen in Java. Primitive sind eingebaute Typen (Boolean, Int usw.) und Objekte (Klassen) sind vom Benutzer erstellte Typen.

Primitive und Objekte können veränderlich oder unveränderlich sein, wenn sie als Elementvariablen in der Implementierung einer Klasse definiert sind.

Viele Leute denken, dass Primitive und Objektvariablen, die einen finalen Modifikator vor sich haben, unveränderlich sind, aber das ist nicht genau richtig. Final bedeutet also fast nicht unveränderlich für Variablen. Siehe Beispiel hier
http://www.siteconsortium.com/h/D0000F.php .

0
JTHouseCat

Wenn Sie Referenzen eines Arrays oder einer Zeichenfolge zurückgeben, kann die Außenwelt den Inhalt in diesem Objekt ändern und es daher als veränderbares (änderbares) Objekt festlegen.

0
user1923551

Mutable Instanzen werden als Referenz übergeben.

Unveränderlich Instanzen werden als Wert übergeben.

Abstraktes Beispiel. Angenommen, auf meiner Festplatte existiert eine Datei mit dem Namen txtfile. Wenn Sie mich nun nach txtfile fragen, kann ich es in zwei Modi zurückgeben:

  1. Erstellen Sie eine Verknüpfung zu txtfile und geben Sie eine Verknüpfung zu Ihnen ein, oder
  2. Nehmen Sie eine Kopie für txtfile und senden Sie eine Kopie an Sie.

Im ersten Modus ist return txtfile eine veränderbare Datei, da Sie beim Ändern der Verknüpfungsdatei auch die Originaldatei ändern. Vorteil dieses Modus ist, dass jede zurückgegebene Verknüpfung weniger Speicher benötigt (auf RAM oder auf Festplatte)) und der Nachteil ist, dass jeder (nicht nur ich, der Besitzer) die Berechtigung hat, den Dateiinhalt zu ändern.

Im zweiten Modus ist return txtfile eine unveränderliche Datei, da sich alle Änderungen in der empfangenen Datei nicht auf die Originaldatei beziehen. Der Vorteil dieses Modus ist, dass nur ich (der Besitzer) die Originaldatei ändern kann, und der Nachteil ist, dass für jede zurückgegebene Kopie Speicher benötigt wird (in RAM oder auf Festplatte)).

0
Teodor