Ich liebe das unveränderliche "Muster" aufgrund seiner Stärken und in der Vergangenheit fand ich es vorteilhaft, Systeme mit unveränderlichen Datentypen (einige, die meisten oder sogar alle) zu entwerfen. Wenn ich das tue, schreibe ich oft weniger Fehler und das Debuggen ist viel einfacher.
Allerdings scheuen meine Kollegen im Allgemeinen unveränderlich. Sie sind überhaupt nicht unerfahren (weit davon entfernt), aber sie schreiben Datenobjekte auf klassische Weise - private Mitglieder mit einem Getter und einem Setter für jedes Mitglied. Normalerweise nehmen ihre Konstruktoren dann keine Argumente oder nehmen aus Bequemlichkeitsgründen nur einige Argumente. So oft sieht das Erstellen eines Objekts so aus:
Foo a = new Foo();
a.setProperty1("asdf");
a.setProperty2("bcde");
Vielleicht machen sie das überall. Vielleicht definieren sie nicht einmal einen Konstruktor, der diese beiden Zeichenfolgen verwendet, egal wie wichtig sie sind. Und vielleicht ändern sie den Wert dieser Saiten später nicht und müssen es auch nie. Klar, wenn diese Dinge wahr sind, wäre das Objekt besser als unveränderlich zu gestalten, oder? (Der Konstruktor übernimmt die beiden Eigenschaften, überhaupt keine Setter.)
Wie entscheiden Sie, ob ein Objekttyp unveränderlich gestaltet werden soll? Gibt es eine Reihe guter Kriterien, nach denen man es beurteilen kann?
Ich überlege derzeit, ob ich einige Datentypen in meinem eigenen Projekt auf unveränderlich umstellen soll, aber ich müsste es meinen Kollegen gegenüber rechtfertigen, und die Daten in den Typen könnten sich (sehr selten) ändern - zu welchem Zeitpunkt können Sie sich natürlich ändern auf unveränderliche Weise (erstellen Sie ein neues Objekt und kopieren Sie die Eigenschaften des alten Objekts, mit Ausnahme derjenigen, die Sie ändern möchten). Aber ich bin mir nicht sicher, ob dies nur meine Liebe zu Unveränderlichen ist oder ob es einen tatsächlichen Bedarf gibt, von ihnen zu profitieren.
quelle
Antworten:
Scheint, als würden Sie sich rückwärts nähern. Man sollte standardmäßig unveränderlich sein. Machen Sie ein Objekt nur dann veränderbar, wenn Sie es unbedingt als unveränderliches Objekt verwenden müssen / können.
quelle
Der Hauptvorteil von unveränderlichen Objekten ist die Gewährleistung der Gewindesicherheit. In einer Welt, in der mehrere Kerne und Threads die Norm sind, ist dieser Vorteil sehr wichtig geworden.
Die Verwendung von veränderlichen Objekten ist jedoch sehr praktisch. Ihre Leistung ist gut. Solange Sie sie nicht über separate Threads modifizieren und ein gutes Verständnis für Ihre Arbeit haben, sind sie recht zuverlässig.
quelle
Es gibt zwei Hauptmethoden, um zu entscheiden, ob ein Objekt unveränderlich ist.
a) Basierend auf der Art des Objekts
Es ist einfach, diese Situationen zu erfassen, da wir wissen, dass sich diese Objekte nach ihrer Erstellung nicht ändern werden. Wenn Sie beispielsweise eine
RequestHistory
Entität haben und naturgemäß Entitäten nicht ändern, sobald sie erstellt wurden. Diese Objekte können einfach als unveränderliche Klassen entworfen werden. Beachten Sie, dass das Anforderungsobjekt veränderbar ist, da es seinen Status ändern kann und wem es im Laufe der Zeit usw. zugewiesen wird, der Anforderungsverlauf sich jedoch nicht ändert. Beispielsweise wurde in der letzten Woche ein Verlaufselement erstellt, als es vom Status "Eingereicht" in den Status "Zugewiesen" verschoben wurde, UND dieses Verlaufselement kann sich nie ändern. Das ist also ein klassischer unveränderlicher Fall.b) Abhängig von der Wahl des Designs, externe Faktoren
Dies ähnelt dem Beispiel java.lang.String. Zeichenfolgen können sich im Laufe der Zeit tatsächlich ändern, wurden jedoch aufgrund von Caching- / Zeichenfolgenpool- / Nebenläufigkeitsfaktoren als unveränderlich festgelegt. In ähnlicher Weise kann das Caching / Concurrency usw. eine gute Rolle dabei spielen, ein Objekt immuatibel zu machen, wenn das Caching / Concurrency und die damit verbundene Leistung in der Anwendung von entscheidender Bedeutung sind. Diese Entscheidung sollte jedoch sehr sorgfältig getroffen werden, nachdem alle Auswirkungen analysiert wurden.
Der Hauptvorteil von unveränderlichen Objekten besteht darin, dass sie keinem Tumble-Weed-Muster ausgesetzt sind. Das Objekt nimmt während der gesamten Lebensdauer keine Änderungen auf und erleichtert die Codierung und Wartung erheblich.
quelle
Die Minimierung des Status eines Programms ist von großem Vorteil.
Fragen Sie sie, ob sie einen Typ mit veränderlichen Werten in einer Ihrer Klassen für die temporäre Speicherung einer Clientklasse verwenden möchten.
Wenn sie ja sagen, fragen Sie warum? Der veränderbare Zustand gehört in ein solches Szenario nicht. Das Erzwingen, dass sie den Zustand erstellen, zu dem er tatsächlich gehört, und das Erzwingen eines möglichst eindeutigen Zustands Ihrer Datentypen sind gute Gründe.
quelle
Die Antwort ist etwas sprachabhängig. Ihr Code sieht aus wie Java, wo dieses Problem so schwierig wie möglich ist. In Java können Objekte nur als Referenz übergeben werden, und der Klon ist vollständig fehlerhaft.
Es gibt keine einfache Antwort, aber mit Sicherheit möchten Sie Objekte mit kleinem Wert unveränderlich machen. Java hat Strings korrekt unveränderlich gemacht, aber Datum und Kalender fälschlicherweise veränderlich gemacht.
Machen Sie also auf jeden Fall Objekte mit geringem Wert unveränderlich und implementieren Sie einen Kopierkonstruktor. Vergiss alles über Cloneable, es ist so schlecht gestaltet, dass es nutzlos ist.
Wenn es für Objekte mit größerem Wert unpraktisch ist, sie unveränderlich zu machen, können Sie sie einfach kopieren.
quelle
Etwas kontraintuitiv ist es ein ziemlich gutes Argument, dass es egal ist, ob die Objekte unveränderlich sind oder nicht, niemals die Zeichenfolgen später ändern zu müssen. Der Programmierer behandelt sie bereits als effektiv unveränderlich, unabhängig davon, ob der Compiler sie erzwingt oder nicht.
Unveränderlichkeit tut normalerweise nicht weh, hilft aber auch nicht immer. Sie können leicht feststellen, ob Ihr Objekt von der Unveränderlichkeit profitiert, wenn Sie jemals eine Kopie des Objekts erstellen oder einen Mutex erwerben müssen, bevor Sie es ändern . Wenn es sich nie ändert, dann verschafft Unveränderlichkeit Ihnen nichts und macht die Dinge manchmal komplizierter.
Sie tun einen guten Punkt über das Risiko haben , ein Objekt in einem ungültigen Zustand konstruieren, aber das ist wirklich ein anderes Thema von Unveränderlichkeit. Ein Objekt kann sowohl wandelbar sein und immer in einem gültigen Zustand nach dem Bau.
Die Ausnahme von dieser Regel ist, dass Java weder benannte Parameter noch Standardparameter unterstützt. Daher kann es manchmal unhandlich werden, eine Klasse zu entwerfen, die ein gültiges Objekt nur mit überladenen Konstruktoren garantiert. Nicht so sehr mit dem Fall mit zwei Eigenschaften, aber es gibt auch etwas zu sagen, was die Konsistenz betrifft, wenn dieses Muster in anderen Teilen Ihres Codes bei ähnlichen, aber größeren Klassen häufig vorkommt.
quelle
Object
Funktionen" zugegriffen. Eine direkte Änderung eines Objekts wäre semantisch nicht anders, als die Referenz mit einer Referenz auf ein neues Objekt zu überschreiben, das es war identisch, aber für die angegebene Änderung. Eine der größten semantischen Schwächen in Java, IMHO, ist, dass es keine Mittel gibt, mit denen Code anzeigen kann, dass eine Variable nur den einzigen nicht-flüchtigen Verweis auf etwas enthalten soll. Verweise sollten nur an Methoden übergeben werden können, die nach ihrer Rückkehr möglicherweise keine Kopie behalten können.Möglicherweise habe ich eine zu niedrige Sichtweise und wahrscheinlich, weil ich C und C ++ verwende, was es nicht so einfach macht, alles unveränderlich zu machen, aber ich sehe unveränderliche Datentypen als Optimierungsdetail , um mehr zu schreiben Effiziente Funktionen, die frei von Nebenwirkungen sind und Funktionen wie das Rückgängigmachen von Systemen und die zerstörungsfreie Bearbeitung auf einfache Weise bereitstellen.
Dies könnte zum Beispiel enorm teuer sein:
... wenn
Mesh
nicht als persistente Datenstruktur konzipiert, sondern als Datentyp, der vollständig kopiert werden musste (was in manchen Szenarien Gigabyte umfassen kann), selbst wenn wir nur a ändern werden Teil davon (wie im obigen Szenario, in dem wir nur Scheitelpunktpositionen ändern).Dann greife ich zur Unveränderlichkeit und gestalte die Datenstruktur so, dass nicht veränderte Teile davon flach kopiert und die Referenzen gezählt werden können, damit die oben beschriebene Funktion einigermaßen effizient ist, ohne dass ganze Netze tief kopiert werden müssen, während die Datenstruktur noch geschrieben werden kann Die Funktion ist nebenwirkungsfrei, was die Thread-Sicherheit, die Ausnahmesicherheit, die Möglichkeit, den Vorgang rückgängig zu machen, ihn zerstörungsfrei anzuwenden usw. dramatisch vereinfacht.
In meinem Fall ist es zu kostspielig (zumindest vom Standpunkt der Produktivität aus), alles unveränderlich zu machen, daher speichere ich es für die Klassen, die zu teuer sind, um sie vollständig zu kopieren. Bei diesen Klassen handelt es sich normalerweise um umfangreiche Datenstrukturen wie Maschen und Bilder. Im Allgemeinen verwende ich eine veränderbare Schnittstelle, um Änderungen an diesen über ein "Builder" -Objekt auszudrücken und eine neue unveränderliche Kopie zu erhalten. Und ich tue nicht so viel, um unveränderliche Garantien auf der zentralen Ebene der Klasse zu erreichen, sondern um mir zu helfen, die Klasse in Funktionen einzusetzen, die frei von Nebenwirkungen sein können. Mein Wunsch,
Mesh
unveränderlich zu machen, besteht nicht darin, Netze unveränderlich zu machen, sondern es zu ermöglichen, auf einfache Weise Funktionen zu schreiben, die frei von Nebeneffekten sind, die ein Netz eingeben und ein neues ausgeben, ohne dafür einen hohen Speicher- und Rechenaufwand zu zahlen.Als Ergebnis habe ich nur 4 unveränderliche Datentypen in meiner gesamten Codebasis, und sie sind alle kräftige Datenstrukturen, aber ich benutze sie stark, um mir beim Schreiben von Funktionen zu helfen, die frei von Nebenwirkungen sind. Diese Antwort könnte zutreffen, wenn Sie wie ich in einer Sprache arbeiten, die es nicht so einfach macht, alles unveränderlich zu machen. In diesem Fall können Sie sich darauf konzentrieren, den Großteil Ihrer Funktionen so zu gestalten, dass Nebenwirkungen vermieden werden. In diesem Fall möchten Sie möglicherweise bestimmte Datenstrukturen (PDS-Typen) als Optimierungsdetail unveränderlich machen, um teure Vollkopien zu vermeiden. In der Zwischenzeit, wenn ich eine Funktion wie diese habe:
... dann habe ich keine Versuchung, Vektoren unveränderlich zu machen, da sie billig genug sind, um sie nur vollständig zu kopieren. Hier kann kein Leistungsvorteil erzielt werden, wenn Vektoren in unveränderliche Strukturen umgewandelt werden, mit denen nicht modifizierte Teile kopiert werden können. Diese Kosten würden die Kosten für das Kopieren des gesamten Vektors aufwiegen.
quelle
Wenn Foo nur zwei Eigenschaften hat, ist es einfach, einen Konstruktor zu schreiben, der zwei Argumente akzeptiert. Angenommen, Sie fügen eine andere Eigenschaft hinzu. Dann müssen Sie einen weiteren Konstruktor hinzufügen:
Zu diesem Zeitpunkt ist es noch überschaubar. Aber was ist, wenn es 10 Immobilien gibt? Sie möchten keinen Konstruktor mit 10 Argumenten. Sie können das Builder-Muster zum Erstellen von Instanzen verwenden, dies erhöht jedoch die Komplexität. Zu diesem Zeitpunkt werden Sie sich überlegen: "Warum habe ich nicht einfach für alle Eigenschaften Setter hinzugefügt, wie es normale Leute tun"?
quelle