Wenn unveränderliche Objekte gut sind, warum schaffen die Leute dann immer wieder veränderbare Objekte? [geschlossen]


250

Wenn unveränderliche Objekte¹ gut und einfach sind und Vorteile bei der gleichzeitigen Programmierung bieten, warum erstellen Programmierer dann immer wieder veränderbare Objekte²?

Ich habe vier Jahre Erfahrung in der Java-Programmierung und aus meiner Sicht ist das erste, was Leute nach dem Erstellen einer Klasse tun,, Getter und Setter in der IDE zu generieren (und sie somit veränderbar zu machen). Gibt es einen Mangel an Bewusstsein oder können wir in den meisten Szenarien nicht mit veränderlichen Objekten umgehen?


¹ Unveränderliches Objekt ist ein Objekt, dessen Status nach seiner Erstellung nicht mehr geändert werden kann.
² Ein veränderbares Objekt ist ein Objekt, das nach seiner Erstellung geändert werden kann.


42
Ich denke, neben legitimen Gründen (wie von Péter weiter unten erwähnt) ist "fauler Entwickler" ein häufigerer Grund als "dummer" Entwickler. Und vor "dummer Entwickler" steht auch "uninformierter Entwickler".
Joachim Sauer

201
Für jeden evangelikalen Programmierer / Blogger gibt es 1000 begeisterte Blogleser, die sich sofort neu erfinden und die neuesten Techniken anwenden. Für jeden von ihnen gibt es 10.000 Programmierer, die mit der Nase bis zum Schliff arbeiten und Produkte aus der Tür holen. Diese Typen verwenden bewährte Techniken, die seit Jahren für sie funktionieren. Sie warten, bis sich neue Techniken durchgesetzt haben und zeigen den tatsächlichen Nutzen, bevor sie diese anwenden. Nenne sie nicht dumm und sie sind alles andere als faul, sondern "beschäftigt".
Binary Worrier

22
@BinaryWorrier: Unveränderliche Objekte sind kaum eine "neue Sache". Sie wurden möglicherweise nicht häufig für Domänenobjekte verwendet, aber Java und C # hatten sie von Anfang an. Auch: "faul" ist nicht immer ein schlechtes Wort, manche Arten von "faul" sind ein absoluter Vorteil für einen Entwickler.
Joachim Sauer

11
@Joachim: Ich denke, es ist ziemlich offensichtlich, dass "faul" im obigen abwertenden Sinne verwendet wurde :) Auch unveränderliche Objekte (wie Lambda Calculus und OOP damals - ja, ich bin so alt) müssen nicht neu sein plötzlich Geschmack des Monats werden . Ich behaupte nicht, dass sie eine schlechte Sache sind (sie sind es nicht) oder dass sie nicht ihren Platz haben (sie haben es offensichtlich) wurde als inbrünstig selbst konvertiert (nicht beschuldigt Sie für den "faulen" Kommentar, ich weiß, Sie haben versucht, es zu mildern).
Binary Worrier

116
-1, unveränderliche Objekte sind nicht 'gut'. Nur für bestimmte Situationen mehr oder weniger geeignet. Jeder, der Ihnen die eine oder andere Technik erzählt, ist objektiv "gut" oder "schlecht" gegenüber einer anderen in allen Situationen und verkauft Ihnen die Religion.
GrandmasterB

Antworten:


326

Sowohl veränderbare als auch unveränderbare Objekte haben ihre eigenen Verwendungen, Vor- und Nachteile.

Unveränderliche Gegenstände machen das Leben in vielen Fällen tatsächlich einfacher. Sie eignen sich insbesondere für Werttypen, bei denen Objekte keine Identität haben, sodass sie leicht ersetzt werden können. Und sie können die gleichzeitige Programmierung sicherer und sauberer machen (die meisten der notorisch schwer zu findenden Fehler im Zusammenhang mit der Parallelität werden letztendlich durch den veränderlichen Status verursacht, der von den Threads gemeinsam genutzt wird). Bei großen und / oder komplexen Objekten kann das Erstellen einer neuen Kopie des Objekts für jede einzelne Änderung sehr kostspielig und / oder mühsam sein. Bei Objekten mit einer eindeutigen Identität ist das Ändern vorhandener Objekte viel einfacher und intuitiver als das Erstellen einer neuen, geänderten Kopie.

Denken Sie an eine Spielfigur. In Spielen hat Geschwindigkeit oberste Priorität. Wenn Sie Ihre Spielcharaktere also mit veränderlichen Objekten darstellen, wird Ihr Spiel höchstwahrscheinlich erheblich schneller ausgeführt als bei einer alternativen Implementierung, bei der bei jeder kleinen Änderung eine neue Kopie des Spielcharakters erstellt wird.

Darüber hinaus basiert unsere Wahrnehmung der realen Welt unweigerlich auf veränderlichen Objekten. Wenn Sie Ihr Auto an der Tankstelle mit Kraftstoff tanken, nehmen Sie es die ganze Zeit über als dasselbe Objekt wahr (dh seine Identität bleibt erhalten, während sich sein Zustand ändert) - nicht als ob das alte Auto mit leerem Tank durch ein konsekutives neues ersetzt worden wäre Autoinstanzen, deren Panzer nach und nach immer voller wird. Wenn wir also eine reale Domäne in einem Programm modellieren, ist es normalerweise einfacher und unkomplizierter, das Domänenmodell mit veränderlichen Objekten zu implementieren, um reale Entitäten darzustellen.

Abgesehen von all diesen legitimen Gründen ist die Trägheit des Geistes, auch bekannt als Widerstand gegen Veränderungen, leider die wahrscheinlichste Ursache, warum Menschen ständig veränderliche Objekte erschaffen. Beachten Sie, dass die meisten Entwickler von heute lange bevor die Unveränderlichkeit (und das darin enthaltene Paradigma, die funktionale Programmierung) in ihrem Einflussbereich "trendy" wurde, geschult wurden und ihr Wissen über neue Werkzeuge und Methoden unseres Handels nicht auf dem neuesten Stand halten - Tatsächlich widersetzen sich viele von uns Menschen positiv neuen Ideen und Prozessen. "Ich programmiere seit nn Jahren so und mir sind die neuesten dummen Trends egal!"


27
Stimmt. Besonders in der GUI-Programmierung sind veränderbare Objekte sehr praktisch.
Florian Salihovic

23
Es ist nicht nur Widerstand, ich bin sicher, dass viele Entwickler gerne das Neueste und Beste ausprobieren würden, aber wie oft entstehen neue Projekte in der Umgebung eines durchschnittlichen Entwicklers, in der sie diese neuen Praktiken anwenden können? Nicht jeder kann oder wird ein Hobbyprojekt schreiben, um den unveränderlichen Zustand zu testen.
Steven Evers

29
Zwei kleine Vorbehalte dazu: (1) Nehmen Sie die sich bewegende Spielfigur. Die PointKlasse in .NET ist zum Beispiel unveränderlich, aber das Erstellen neuer Punkte als Ergebnis von Änderungen ist einfach und daher erschwinglich. Das Animieren eines unveränderlichen Charakters kann durch Entkoppeln der "beweglichen Teile" sehr billig gemacht werden (aber ja, ein Aspekt ist dann veränderlich). (2) „große und / oder komplexe Objekte“ können sehr wohl unveränderlich sein. Saiten sind oft groß und profitieren normalerweise von der Unveränderlichkeit. Ich habe einmal eine komplexe Graph-Klasse umgeschrieben, um unveränderlich zu sein und den Code einfacher und effizienter zu machen. In solchen Fällen ist ein veränderlicher Builder der Schlüssel.
Konrad Rudolph

4
@KonradRudolph, gute Punkte, danke. Ich wollte nicht ausschließen, dass Unveränderlichkeit in komplexen Objekten verwendet wird, aber die korrekte und effiziente Implementierung einer solchen Klasse ist keine triviale Aufgabe, und der zusätzliche Aufwand ist möglicherweise nicht immer gerechtfertigt.
Péter Török,

6
Sie machen einen guten Punkt über Staat vs Identität. Aus diesem Grund hat Rich Hickey (Autor von Clojure) die beiden in Clojure auseinander gebrochen. Man könnte argumentieren, dass das Auto, das Sie mit 1/2 Tank haben, nicht dasselbe Auto ist wie das mit 1/4 Tank. Sie haben die gleiche Identität, aber sie sind nicht die gleichen, jeder "Tick" unserer Wirklichkeitszeit erzeugt einen Klon jedes Objekts in unserer Welt, unser Gehirn näht diese dann einfach mit einer gemeinsamen Identität zusammen. Clojure hat Refs, Atome, Agenten usw., um die Zeit darzustellen. Und Karten, Vektoren und Listen für die aktuelle Zeit.
Timothy Baldridge

130

Ich denke, Sie haben alle die offensichtlichste Antwort verpasst. Die meisten Entwickler erstellen veränderbare Objekte, da die Veränderbarkeit in imperativen Sprachen der Standard ist. Die meisten von uns haben etwas Besseres mit ihrer Zeit zu tun, als den Code ständig von den Standardeinstellungen zu entfernen - richtiger oder nicht. Und Unveränderlichkeit ist kein Allheilmittel mehr als irgendein anderer Ansatz. Es macht einige Dinge einfacher, andere jedoch viel schwieriger, wie einige Antworten bereits gezeigt haben.


9
In den meisten modernen IDEs, die ich kenne, ist praktisch derselbe Aufwand erforderlich, um nur Getter zu generieren, als um sowohl Getter als auch Setter zu generieren. Obwohl es wahr ist , dass das Hinzufügen final, constnimmt usw. ein bisschen mehr Mühe ... es sei denn , Sie eine Codevorlage :-) einrichten
Péter Török

10
@ PéterTörök Es ist nicht nur die zusätzliche Anstrengung, sondern auch die Tatsache, dass Ihre Programmierkollegen Sie aufhängen möchten, weil sie Ihren Programmierstil für ihre Erfahrung so fremd finden. Das entmutigt auch so etwas.
Onorio Catenacci

5
Dies könnte durch mehr Kommunikation und Aufklärung überwunden werden. Beispielsweise ist es ratsam, mit den Teammitgliedern über einen neuen Codierungsstil zu sprechen oder diesen vorzustellen, bevor er tatsächlich in eine vorhandene Codebasis eingefügt wird. Es ist wahr, dass ein gutes Projekt einen gemeinsamen Codierungsstil hat, der nicht nur eine Mischung der Codierungsstile ist, die von jedem (früheren und gegenwärtigen) Projektmitglied bevorzugt werden. Das Einführen oder Ändern von Codierungsidiomen sollte also eine Teamentscheidung sein.
Péter Török,

3
@ PéterTörök In einer imperativen Sprache sind veränderbare Objekte das Standardverhalten. Wenn Sie nur unveränderliche Objekte wollen, ist es am besten, zu einer funktionalen Sprache zu wechseln.
Emory

3
@ PéterTörök Es ist ein wenig naiv anzunehmen, dass die Integration von Unveränderlichkeit in ein Programm nichts anderes bedeutet, als alle Setter fallen zu lassen. Sie brauchen immer noch eine Möglichkeit, um den Status des Programms schrittweise zu ändern, und dafür benötigen Sie Builder, Eis am Stiel-Unveränderlichkeit oder veränderbare Proxys, die alle etwa das 1-Milliarden-fache des Aufwandes erfordern, den das Löschen der Setter mit sich brachte.
Asad Saeeduddin

49
  1. Es gibt einen Platz für die Veränderbarkeit. Domänengesteuerte Entwurfsprinzipien bieten ein solides Verständnis dafür, was veränderlich und was unveränderlich sein sollte. Wenn Sie darüber nachdenken, werden Sie feststellen, dass es unpraktisch ist, sich ein System vorzustellen, in dem jede Änderung des Zustands eines Objekts dessen Zerstörung und Neuzusammensetzung erfordert, sowie jedes Objekt, das darauf verweist. Bei komplexen Systemen kann dies leicht dazu führen, dass der Objektgraph des gesamten Systems vollständig gelöscht und neu erstellt wird

  2. Die meisten Entwickler machen nichts, wo die Leistungsanforderungen so bedeutend sind, dass sie sich auf die Parallelität konzentrieren müssen (oder auf viele andere Probleme, die von den Informierten allgemein als gute Praxis angesehen werden).

  3. Es gibt einige Dinge, die Sie mit unveränderlichen Objekten einfach nicht tun können, z. B. bidirektionale Beziehungen. Sobald Sie einen Zuordnungswert für ein Objekt festgelegt haben, ändert sich dessen Identität. Sie setzen also den neuen Wert für das andere Objekt und dieser ändert sich ebenfalls. Das Problem ist, dass die Referenz des ersten Objekts nicht mehr gültig ist, da eine neue Instanz erstellt wurde, um das Objekt mit der Referenz darzustellen. Wenn Sie dies fortsetzen, würde dies nur zu unendlichen Regressionen führen. Nachdem ich Ihre Frage gelesen hatte, habe ich eine kleine Fallstudie durchgeführt. So sieht es aus. Haben Sie einen alternativen Ansatz, der eine solche Funktionalität bei gleichzeitiger Aufrechterhaltung der Unveränderlichkeit ermöglicht?

        public class ImmutablePerson { 
    
         public ImmutablePerson(string name, ImmutableEventList eventsToAttend)
         {
              this.name = name;
              this.eventsToAttend = eventsToAttend;
         }
         private string name;
         private ImmutableEventList eventsToAttend;
    
         public string Name { get { return this.name; } }
    
         public ImmutablePerson RSVP(ImmutableEvent immutableEvent){
             // the person is RSVPing an event, thus mutating the state 
             // of the eventsToAttend.  so we need a new person with a reference
             // to the new Event
             ImmutableEvent newEvent = immutableEvent.OnRSVPReceived(this);
             ImmutableEventList newEvents = this.eventsToAttend.Add(newEvent));
             var newSelf = new ImmutablePerson(name, newEvents);
             return newSelf;
         }
        }
    
        public class ImmutableEvent { 
         public ImmutableEvent(DateTime when, ImmutablePersonList peopleAttending, ImmutablePersonList peopleNotAttending){
             this.when = when;     
             this.peopleAttending = peopleAttending;
             this.peopleNotAttending = peopleNotAttending;
         }
         private DateTime when; 
         private ImmutablePersonList peopleAttending;
         private ImmutablePersonList peopleNotAttending;
         public ImmutableEvent OnReschedule(DateTime when){
               return new ImmutableEvent(when,peopleAttending,peopleNotAttending);
         }
         //  notice that this will be an infinite loop, because everytime one counterpart
         //  of the bidirectional relationship is added, its containing object changes
         //  meaning it must re construct a different version of itself to 
         //  represent the mutated state, the other one must update its
         //  reference thereby obsoleting the reference of the first object to it, and 
         //  necessitating recursion
         public ImmutableEvent OnRSVPReceived(ImmutablePerson immutablePerson){
               if(this.peopleAttending.Contains(immutablePerson)) return this;
               ImmutablePersonList attending = this.peopleAttending.Add(immutablePerson);
               ImmutablePersonList notAttending = this.peopleNotAttending.Contains( immutablePerson ) 
                                    ? peopleNotAttending.Remove(immutablePerson)
                                    : peopleNotAttending;
               return new ImmutableEvent(when, attending, notAttending);
         }
        }
        public class ImmutablePersonList
        {
          private ImmutablePerson[] immutablePeople;
          public ImmutablePersonList(ImmutablePerson[] immutablePeople){
              this.immutablePeople = immutablePeople;
          }
          public ImmutablePersonList Add(ImmutablePerson newPerson){
              if(this.Contains(newPerson)) return this;
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length];
              for(var i=0;i<immutablePeople.Length;i++)
                  newPeople[i] = this.immutablePeople[i];
              newPeople[immutablePeople.Length] = newPerson;
          }
          public ImmutablePersonList Remove(ImmutablePerson newPerson){
              if(immutablePeople.IndexOf(newPerson) != -1)
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutablePeople[i] == newPerson;
                 newPeople[i] = this.immutablePeople[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutablePersonList(newPeople);
          }
          public bool Contains(ImmutablePerson immutablePerson){ 
             return this.immutablePeople.IndexOf(immutablePerson) != -1;
          } 
        }
        public class ImmutableEventList
        {
          private ImmutableEvent[] immutableEvents;
          public ImmutableEventList(ImmutableEvent[] immutableEvents){
              this.immutableEvents = immutableEvents;
          }
          public ImmutableEventList Add(ImmutableEvent newEvent){
              if(this.Contains(newEvent)) return this;
              ImmutableEvent[] newEvents= new ImmutableEvent[immutableEvents.Length];
              for(var i=0;i<immutableEvents.Length;i++)
                  newEvents[i] = this.immutableEvents[i];
              newEvents[immutableEvents.Length] = newEvent;
          }
          public ImmutableEventList Remove(ImmutableEvent newEvent){
              if(immutableEvents.IndexOf(newEvent) != -1)
              ImmutableEvent[] newEvents = new ImmutableEvent[immutableEvents.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutableEvents[i] == newEvent;
                 newEvents[i] = this.immutableEvents[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutableEventList(newPeople);
          }
          public bool Contains(ImmutableEvent immutableEvent){ 
             return this.immutableEvent.IndexOf(immutableEvent) != -1;
          } 
        }
    

2
@AndresF. Wenn Sie eine andere Sichtweise haben, wie komplexe Diagramme mit bidirektionalen Beziehungen nur mit unveränderlichen Objekten verwaltet werden, würde ich sie gerne hören. (Ich nehme an, wir können uns darauf einigen, dass eine Sammlung / ein Array ein Objekt ist)
smartcaveman

2
@AndresF., (1) Meine erste Aussage war nicht universell, also nicht falsch. Ich lieferte tatsächlich ein Codebeispiel, um zu erklären, wie es in bestimmten Fällen, die in der Anwendungsentwicklung häufig vorkommen, unbedingt zutraf. (2) Bidirektionale Beziehungen erfordern im Allgemeinen eine Veränderlichkeit. Ich glaube nicht, dass Java der Schuldige ist. Wie gesagt, ich würde gerne alle konstruktiven Alternativen bewerten, die Sie vorschlagen würden, aber an dieser Stelle klingen Ihre Kommentare sehr nach "Sie irren sich, weil ich es gesagt habe".
Smartcaveman

14
@smartcaveman Über (2) bin ich auch anderer Meinung: Im Allgemeinen ist eine "bidirektionale Beziehung" ein mathematisches Konzept, das orthogonal zur Wandlungsfähigkeit ist. Wie gewöhnlich in Java implementiert, erfordert es eine Wandlungsfähigkeit (da habe ich Ihnen zugestimmt). Ich kann mir jedoch eine alternative Implementierung vorstellen: eine Beziehungsklasse zwischen den beiden Objekten mit einem Konstruktor Relationship(a, b); Zum Zeitpunkt der Erstellung der Beziehung sind beide Entitäten a& bbereits vorhanden, und die Beziehung selbst ist ebenfalls unveränderlich. Ich sage nicht, dass dieser Ansatz in Java praktisch ist. nur dass es möglich ist.
Andres F.

4
@AndresF. Also, basierend auf dem, was du sagst, Wenn Rdas Relationship(a,b)und beides ist aund bunveränderlich ist, würde weder ein Verweis darauf anoch bein Verweis darauf enthalten R. Damit dies funktioniert, müsste die Referenz an einer anderen Stelle gespeichert werden (z. B. in einer statischen Klasse). Verstehe ich Ihre Absicht richtig?
Smartcaveman

9
Es ist möglich, eine bidirektionale Beziehung für unveränderliche Daten zu speichern, wie Chthulhu durch Faulheit hervorhebt. Hier ist eine Möglichkeit, dies zu tun: haskell.org/haskellwiki/Tying_the_Knot
Thomas Eding

36

Ich habe "rein funktionale Datenstrukturen" gelesen und festgestellt, dass es einige Datenstrukturen gibt, die mit veränderlichen Objekten viel einfacher zu implementieren sind.

Um einen binären Suchbaum zu implementieren, müssen Sie jedes Mal einen neuen Baum zurückgeben: Ihr neuer Baum musste eine Kopie von jedem Knoten erstellen, der geändert wurde (die nicht geänderten Zweige werden gemeinsam genutzt). Für Ihre Einfügefunktion ist das nicht so schlimm, aber für mich wurden die Dinge ziemlich schnell ineffizient, als ich anfing, an Löschen und Neuausgleichen zu arbeiten.

Die andere Sache, die Sie beachten müssen, ist, dass Sie jahrelang objektorientierten Code schreiben und nie wirklich erkennen können, wie schrecklich der gemeinsam genutzte veränderbare Zustand sein kann, wenn Ihr Code nicht auf eine Weise ausgeführt wird, die Parallelitätsprobleme aufdeckt.


3
Ist das das Okasaki-Buch?
Mechanische Schnecke

ja. ein bisschen trocken, aber eine Menge guter Infos ...
Paul Sanwald

4
Witzigerweise dachte ich immer, Okasakis rot / schwarzer Baum sei so viel einfacher. 10 Zeilen oder so. Ich denke, der größte Vorteil ist, wenn Sie die alte Version auch beibehalten möchten.
Thomas Ahle

Obwohl der letzte Satz wahrscheinlich in der Vergangenheit zutrifft, ist nicht klar, ob er angesichts der aktuellen Hardwaretrends usw. auch in Zukunft zutrifft.
jk.

29

Aus meiner Sicht ist das ein Mangel an Bewusstsein. Wenn Sie sich andere bekannte JVM-Sprachen (Scala, Clojure) ansehen, werden veränderbare Objekte nur selten im Code angezeigt. Daher werden sie in Szenarien verwendet, in denen Single-Threading nicht ausreicht.

Ich lerne derzeit Clojure und habe ein wenig Erfahrung in Scala (4 Jahre und mehr in Java) und Ihr Codierungsstil ändert sich aufgrund des Bewusstseins für den Status.


Vielleicht wäre "bekannt" eher als "beliebt" eine bessere Wortwahl.
Den

Ja, das ist richtig.
Florian Salihovic

5
+1: Ich stimme zu: Nach dem Erlernen von Scala und Haskell neige ich dazu, überall final in Java und const in C ++ zu verwenden. Ich verwende, wenn möglich, auch unveränderbare Objekte, und obwohl veränderbare Objekte immer noch sehr oft benötigt werden, ist es erstaunlich, wie oft Sie unveränderbare Objekte verwenden können.
Giorgio

5
Ich habe diesen Kommentar nach zweieinhalb Jahren gelesen und meine Meinung hat sich zugunsten der Unveränderlichkeit geändert. In meinem aktuellen Projekt (das sich in Python befindet) verwenden wir sehr sehr selten veränderbare Objekte. Auch unsere persistenten Daten sind unveränderlich: Wir erstellen neue Datensätze als Ergebnis einiger Vorgänge und löschen alte Datensätze, wenn sie nicht mehr benötigt werden, aktualisieren jedoch niemals Datensätze auf der Festplatte. Dies hat es unnötig zu erwähnen, dass unsere gleichzeitige Mehrbenutzeranwendung bisher viel einfacher zu implementieren und zu warten war.
Giorgio

13

Ich denke , ein wichtiger Faktor ignoriert wurde: Java Beans verlassen sich stark auf einen bestimmten Stil Objekte mutiert, und (besonders wenn man bedenkt der Quelle) durchaus ein paar Leute scheinen zu nehmen , dass als (oder auch die ) kanonische Beispiel dafür , wie alle Java sollte geschrieben werden.


4
+1, das Getter / Setter-Muster wird viel zu oft als eine Art Standardimplementierung nach der ersten Datenanalyse verwendet.
Jaap

Dies ist wahrscheinlich ein großer Punkt ... "weil das so ist, wie es alle anderen tun" ... also muss es richtig sein. Für ein "Hello World" -Programm ist es wahrscheinlich am einfachsten. Objektzustandsänderungen über eine Reihe von veränderlichen Eigenschaften verwalten ... das ist etwas kniffliger als die Tiefen des "Hello World" -Verständnisses. 20 Jahre später bin ich sehr überrascht, dass der Höhepunkt der Programmierung des 20. Jahrhunderts darin besteht, getX- und setX-Methoden (wie mühsam) über jedes Attribut eines Objekts ohne jegliche Struktur zu schreiben. Es ist nur einen Schritt vom direkten Zugriff auf öffentliche Objekte mit 100% iger Veränderlichkeit entfernt.
Darrell Teague

12

Jedes Java-Unternehmenssystem, an dem ich in meiner Karriere gearbeitet habe, verwendet entweder Hibernate oder die Java Persistence API (JPA). Hibernate und JPA schreiben im Wesentlichen vor, dass Ihr System veränderbare Objekte verwendet, da die gesamte Voraussetzung darin besteht, dass sie Änderungen an Ihren Datenobjekten erkennen und speichern. Für viele Projekte ist die Einfachheit der Entwicklung, die Hibernate mit sich bringt, überzeugender als die Vorteile unveränderlicher Objekte.

Offensichtlich gibt es veränderbare Objekte schon viel länger als den Ruhezustand, so dass der Ruhezustand wahrscheinlich nicht die ursprüngliche Ursache für die Beliebtheit veränderbarer Objekte ist. Vielleicht ließ die Popularität von veränderlichen Objekten Hibernate gedeihen.

Aber wenn heute viele Nachwuchsprogrammierer mit Hibernate oder einem anderen ORM auf Unternehmenssysteme zugreifen, werden sie vermutlich die Gewohnheit haben, veränderbare Objekte zu verwenden. Frameworks wie Hibernate könnten die Popularität von veränderlichen Objekten festigen.


Hervorragender Punkt. Um alles für alle Menschen zu sein, haben solche Frameworks keine andere Wahl, als auf den kleinsten gemeinsamen Nenner zu fallen, reflexionsbasierte Proxies zu verwenden und sich auf den Weg zur Flexibilität zu machen. Dies schafft natürlich Systeme mit wenig bis gar keinen Zustandsübergangsregeln oder einem gemeinsamen Mittel, um sie leider umzusetzen. Ich bin mir nach vielen Projekten nicht ganz sicher, was für Zweckmäßigkeit, Erweiterbarkeit und Korrektheit besser ist. Ich neige dazu, einen Hybrid zu denken. Dynamische ORM-Güte, aber mit einigen Definitionen, für welche Felder und welche Statusänderungen möglich sein sollen.
Darrell Teague

9

Ein wichtiger Punkt, der noch nicht erwähnt wurde, ist, dass es möglich ist, die Identität des Objekts, das diesen Zustand einkapselt, unveränderlich zu machen , wenn der Zustand eines Objekts veränderbar ist .

Viele Programme sind darauf ausgelegt, reale Dinge zu modellieren, die von Natur aus veränderlich sind. Angenommen, eine Variable AllTrucksenthält um 00:51 Uhr einen Verweis auf Objekt # 451, das die Wurzel einer Datenstruktur ist, die angibt, welche Ladung in allen Lastwagen einer Flotte zu diesem Zeitpunkt (00:51 Uhr) enthalten ist, und eine Variable BobsTruckkann verwendet werden, um einen Verweis auf Objekt # 24601 zu erhalten, der auf ein Objekt verweist, das angibt, welche Ladung sich zu diesem Zeitpunkt (00:51 Uhr) in Bobs LKW befindet. Um 00:52 Uhr werden einige Lastwagen (einschließlich Bobs) be- und entladen, und die Datenstrukturen werden aktualisiert, sodass AllTrucksnun eine Referenz auf eine Datenstruktur enthalten ist, die angibt, dass sich die Ladung ab 00:52 Uhr in allen Lastwagen befindet.

Was soll mit passieren BobsTruck?

Wenn die 'Fracht'-Eigenschaft jedes LKW-Objekts unveränderlich ist, repräsentiert das Objekt # 24601 für immer den Zustand, den Bobs LKW um 00:51 Uhr hatte. Wenn BobsTruckein direkter Verweis auf Objekt # 24601 vorhanden ist, wird der aktuelle Status von Bobs Truck nicht mehr angezeigt, es sei denn, der AllTruckszu aktualisierende Code wird ebenfalls aktualisiert BobsTruck. Beachten Sie ferner, BobsTruckdass der Code, der aktualisiert werden kann, AllTrucksnur dann aktualisiert werden kann, wenn der Code explizit dafür programmiert wurde , es sei denn, er ist in einer Form eines veränderlichen Objekts gespeichert .

Wenn man in der Lage sein möchte, BobsTruckden Zustand von Bobs LKW zu beobachten, während alle Objekte unveränderlich BobsTruckbleiben , könnte man eine unveränderliche Funktion sein, die angesichts des Werts, AllTrucksder zu einem bestimmten Zeitpunkt vorliegt oder hatte, den Zustand von Bobs LKW bei ergibt diese Zeit. Man könnte es sogar zwei unveränderliche Funktionen haben lassen, von denen eine wie oben ist und die andere einen Verweis auf einen Flottenstatus und einen neuen LKW-Status akzeptieren und einen Verweis auf einen neuen Flottenstatus zurückgeben würde passte zum alten, nur dass Bobs Truck den neuen Zustand haben würde.

Leider kann es ziemlich nervig und umständlich werden, wenn man eine solche Funktion jedes Mal verwenden muss, wenn man auf den Zustand von Bobs Truck zugreifen möchte. Ein alternativer Ansatz wäre, zu sagen, dass Objekt # 24601 immer und für immer (solange jemand einen Verweis darauf hat) den aktuellen Status von Bobs Truck darstellt. Code, der wiederholt auf den aktuellen Status von Bobs Truck zugreifen möchte, müsste nicht jedes Mal eine zeitaufwendige Funktion ausführen - er könnte einfach eine Suchfunktion ausführen, um herauszufinden, dass Objekt # 24601 Bobs Truck ist, und dann einfach Greifen Sie jederzeit auf dieses Objekt zu, um den aktuellen Status von Bobs Truck zu sehen.

Beachten Sie, dass der funktionale Ansatz in einer Umgebung mit nur einem Thread oder in einer Umgebung mit mehreren Threads nicht ohne Vorteile ist, in der Threads zumeist nur Daten beobachten und nicht ändern. Ein beliebiger Beobachter-Thread, der die in enthaltene Objektreferenz kopiertAllTrucksWenn Sie dann den von Ihnen dargestellten LKW-Status überprüfen, wird der Status aller LKWs ab dem Zeitpunkt angezeigt, an dem die Referenz erfasst wurde. Jedes Mal, wenn ein Beobachter-Thread neuere Daten sehen möchte, kann er die Referenz erneut abrufen. Wenn andererseits der gesamte Zustand der Flotte durch ein einziges unveränderliches Objekt dargestellt würde, würde die Möglichkeit ausgeschlossen, dass zwei Threads gleichzeitig verschiedene Lastwagen aktualisieren, da jeder Thread, wenn er seinen eigenen Geräten überlassen würde, ein neues Objekt mit dem Namen "Flottenzustand" erzeugen würde der neue Zustand seines Lastwagens und die alten Zustände von jedem anderen. Die Richtigkeit kann gewährleistet werden, wenn jeder Thread nur dann CompareExchangezum Aktualisieren verwendet wird , AllTruckswenn er sich nicht geändert hat, und auf einen Fehler reagiertCompareExchangeDurch die Neuerstellung des Statusobjekts und die Wiederholung der Operation. Wenn jedoch mehr als ein Thread versucht, gleichzeitig zu schreiben, ist die Leistung im Allgemeinen schlechter als wenn alle Schreibvorgänge in einem einzigen Thread ausgeführt würden. Je mehr Threads solche gleichzeitigen Operationen ausführen, desto schlechter wird die Leistung.

Wenn einzelne LKW-Objekte veränderlich sind, aber unveränderliche Identitäten aufweisen , wird das Multithread-Szenario übersichtlicher. Es darf immer nur ein Thread gleichzeitig auf einem bestimmten LKW ausgeführt werden. Threads, die auf verschiedenen LKWs ausgeführt werden, können dies jedoch ohne Beeinträchtigung tun. Es gibt zwar Möglichkeiten, ein solches Verhalten zu emulieren, auch wenn unveränderliche Objekte verwendet werden (z. B. können die "AllTrucks" -Objekte so definiert werden, dass zum Festlegen des Zustands des zu XXX gehörenden Lastwagens für SSS lediglich ein Objekt mit der Angabe "Ab [Uhrzeit]" generiert werden muss. der Zustand des Lastwagens, der zu [XXX] gehört, ist jetzt [SSS], der Zustand von allem anderen ist [Alter Wert von AllTrucks]CompareExchangeSchleife würde nicht lange dauern. Andererseits würde die Verwendung einer solchen Datenstruktur die zum Auffinden des Lastwagens einer bestimmten Person erforderliche Zeit erheblich erhöhen. Die Verwendung von veränderlichen Objekten mit unveränderlichen Identitäten vermeidet dieses Problem.


8

Es gibt kein Richtig oder Falsch, es kommt nur darauf an, was Sie bevorzugen. Es gibt einen Grund, warum manche Menschen Sprachen bevorzugen, die ein Paradigma gegenüber einem anderen und ein Datenmodell gegenüber einem anderen bevorzugen. Es hängt nur von Ihren Vorlieben ab und davon, was Sie erreichen möchten (und es ist ein heiliger Gral, nach dem manche Sprachen suchen, wenn Sie beide Ansätze problemlos anwenden können, ohne eingefleischte Fans der einen oder anderen Seite zu entfremden).

Ich denke, die beste und schnellste Möglichkeit, Ihre Frage zu beantworten, besteht darin, dass Sie sich mit den Vor- und Nachteilen von Unveränderlichkeit und Veränderlichkeit befassen .


7

Überprüfen Sie diesen Blog-Beitrag: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Es fasst zusammen, warum unveränderliche Objekte besser als veränderlich sind. Hier ist eine kurze Liste von Argumenten:

  • 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

Soweit ich weiß, verwenden die Leute veränderbare Objekte, weil sie immer noch OOP mit imperativer prozeduraler Programmierung mischen.


5

In Java benötigen unveränderliche Objekte einen Konstruktor, der alle Eigenschaften des Objekts übernimmt (oder der Konstruktor erstellt sie aus anderen Argumenten oder Standardwerten). Diese Eigenschaften sollten als endgültig markiert werden .

Es gibt vier Probleme, die hauptsächlich mit der Datenbindung zusammenhängen :

  1. Java Constructors Reflection-Metadaten behalten die Argumentnamen nicht bei.
  2. Java-Konstruktoren (und -Methoden) haben keine benannten Parameter (auch Labels genannt), daher wird es mit vielen Parametern verwirrend.
  3. Beim Erben eines anderen unveränderlichen Objekts muss die richtige Reihenfolge der Konstruktoren aufgerufen werden. Dies kann ziemlich knifflig sein, wenn Sie einfach aufgeben und eines der Felder nicht endgültig belassen.
  4. Die meisten Bindungstechnologien (wie Spring MVC Data Binding, Hibernate usw.) funktionieren nur mit Standardkonstruktoren ohne Argumente (dies lag daran, dass Annotationen nicht immer vorhanden waren).

Sie können # 1 und # 2 mit Anmerkungen wie @ConstructorPropertiesund durch Erstellen eines anderen veränderlichen Builder-Objekts (normalerweise fließend) entschärfen , um das unveränderliche Objekt zu erstellen.


5

Ich bin überrascht, dass niemand die Vorteile der Leistungsoptimierung erwähnt hat. Je nach Sprache kann ein Compiler beim Umgang mit unveränderlichen Daten eine Reihe von Optimierungen vornehmen, da er weiß, dass sich die Daten niemals ändern werden. Alle möglichen Dinge werden übersprungen, was Ihnen enorme Leistungsvorteile bringt.

Auch unveränderliche Objekte eliminieren fast die gesamte Klasse der State Bugs.

Es ist nicht so groß, wie es sein sollte, weil es schwieriger ist, nicht für jede Sprache gilt und den meisten Menschen Imperative Codierung beigebracht wurde.

Außerdem habe ich festgestellt, dass die meisten Programmierer zufrieden sind und sich neuen Ideen widersetzen, die sie nicht vollständig verstehen. Im Allgemeinen mögen die Menschen keine Veränderung.

Denken Sie auch daran, dass der Zustand der meisten Programmierer schlecht ist. Die meisten Programme, die in freier Wildbahn erstellt werden, sind schrecklich und werden durch mangelndes Verständnis und mangelnde Politik verursacht.


4

Warum nutzen die Leute ein mächtiges Feature? Warum verwenden die Leute Meta-Programmierung, Faulheit oder dynamisches Tippen? Die Antwort ist Bequemlichkeit. Der veränderbare Zustand ist so einfach. Das Update ist so einfach, und der Schwellenwert für die Projektgröße, bei dem der unveränderliche Status produktiver ist als der veränderbare Status, ist ziemlich hoch, sodass Sie die Wahl für eine Weile nicht mehr loswerden.


4

Programmiersprachen sind für die Ausführung durch Computer ausgelegt. Alle wichtigen Bausteine ​​von Computern - CPUs, RAM, Cache, Festplatten - sind veränderbar. Wenn sie nicht (BIOS) sind, sind sie wirklich unveränderlich und Sie können auch keine neuen unveränderlichen Objekte erstellen.

Daher weist jede Programmiersprache, die auf unveränderlichen Objekten basiert, eine Repräsentationslücke in ihrer Implementierung auf. Und für frühe Sprachen wie C war das ein großes Hindernis.


1

Ohne veränderbare Objekte gibt es keinen Staat. Zugegebenermaßen ist dies eine gute Sache, wenn Sie sie verwalten können und die Möglichkeit besteht, dass ein Objekt von mehr als einem Thread referenziert wird. Aber das Programm wird ziemlich langweilig. Bei vielen Programmen, insbesondere bei Webservern, wird die Verantwortung für veränderbare Objekte vermieden, indem die Veränderbarkeit von Datenbanken, Betriebssystemen, Systembibliotheken usw. abgeschoben wird. In der Praxis befreit dies den Programmierer von Veränderbarkeitsproblemen und macht das Web (und andere) Entwicklung erschwinglich. Aber die Veränderbarkeit ist immer noch da.

Im Allgemeinen gibt es drei Arten von Klassen: normale, nicht thread-sichere Klassen, die sorgfältig geschützt und geschützt werden müssen; unveränderliche Klassen, die frei verwendet werden können; und veränderbare, thread-sichere Klassen, die frei verwendet werden können, aber mit äußerster Sorgfalt geschrieben werden müssen. Der erste Typ ist der problematische, wobei die schlimmsten diejenigen sind, von denen angenommen wird , dass sie vom dritten Typ sind. Natürlich sind die ersten Typen einfach zu schreiben.

Normalerweise habe ich viele normale, wandelbare Klassen, die ich sehr genau beobachten muss. In einer Multithread-Situation verlangsamt die erforderliche Synchronisierung alles, auch wenn ich eine tödliche Umarmung vermeiden kann. Daher mache ich normalerweise unveränderliche Kopien der veränderlichen Klasse und übergebe diese an jeden, der sie verwenden kann. Jedes Mal, wenn das Original mutiert, wird eine neue unveränderliche Kopie benötigt, daher stelle ich mir vor, dass ich manchmal hundert Kopien des Originals da draußen habe. Ich bin absolut abhängig von Garbage Collection.

Zusammenfassend sind nicht thread-sichere, veränderbare Objekte in Ordnung, wenn Sie nicht mehrere Threads verwenden. (Multithreading ist jedoch überall unangenehm - seien Sie vorsichtig!) Sie können ansonsten sicher verwendet werden, wenn Sie sie auf lokale Variablen beschränken oder sie rigoros synchronisieren. Wenn Sie sie vermeiden können, indem Sie den bewährten Code anderer Personen (DBs, Systemaufrufe usw.) verwenden, tun Sie dies. Wenn Sie können eine unveränderliche Klasse verwenden, tun dies. Und ich denke , im Allgemeinen sind sich die Leute der Multithreading-Probleme entweder nicht bewusst oder haben (vernünftigerweise) Angst davor und wenden alle Arten von Tricks an, um Multithreading zu vermeiden (oder vielmehr die Verantwortung dafür woanders zu übernehmen).

Als PS spüre ich, dass Java-Getter und -Setter außer Kontrolle geraten. Überprüfen Sie dies aus.


7
Unveränderlicher Zustand ist immer noch Zustand.
Jeremy Heiler

3
@ JeremyHeiler: Stimmt, aber es ist der Zustand von etwas, das veränderlich ist. Wenn nichts mutiert, gibt es nur einen Zustand, der mit keinem Zustand identisch ist.
RalphChapin

1

Viele Leute hatten gute Antworten, daher möchte ich auf etwas hinweisen, das Sie angesprochen haben und das sehr aufmerksam und äußerst wahr ist und das an keiner anderen Stelle erwähnt wurde.

Das automatische Erstellen von Setzern und Gettern ist eine schreckliche, schreckliche Idee, aber es ist der erste Weg, wie prozedural denkende Menschen versuchen, OO in ihre Denkweise zu zwingen. Setter und Getter sowie Eigenschaften sollten nur erstellt werden, wenn Sie sie benötigen, und nicht standardmäßig

Obwohl Sie regelmäßig Getter benötigen, sollten Setter oder beschreibbare Eigenschaften in Ihrem Code nur über ein Builder-Muster vorhanden sein, bei dem sie gesperrt werden, nachdem das Objekt vollständig instanziiert wurde.

Viele Klassen sind nach der Erstellung veränderbar, was in Ordnung ist. Sie sollten ihre Eigenschaften nur nicht direkt manipulieren. Stattdessen sollten sie aufgefordert werden, ihre Eigenschaften durch Methodenaufrufe mit tatsächlicher Geschäftslogik zu manipulieren (Ja, ein Setter ist so ziemlich dasselbe) als direkt die Eigenschaft manipulieren)

Dies gilt nicht wirklich für Code / Sprachen im "Scripting" -Stil, sondern für Code, den Sie für eine andere Person erstellen und von der andere erwartet, dass er im Laufe der Jahre wiederholt gelesen wird. Ich musste in letzter Zeit anfangen, diese Unterscheidung zu treffen, weil ich es so sehr genieße, mit Groovy herumzuspielen, und es gibt einen großen Unterschied bei den Zielen.


1

Veränderbare Objekte werden verwendet, wenn Sie nach der Instanziierung des Objekts mehrere Werte festlegen müssen.

Sie sollten keinen Konstruktor mit beispielsweise sechs Parametern haben. Stattdessen ändern Sie das Objekt mit Setter-Methoden.

Ein Beispiel hierfür ist ein Report-Objekt mit Setzern für Schriftart, Ausrichtung usw.

Kurz gesagt: Mutables sind nützlich, wenn Sie einen hohen Status für ein Objekt festlegen müssen und es nicht praktisch wäre, eine sehr lange Konstruktorsignatur zu haben.

BEARBEITEN: Builder-Muster können verwendet werden, um den gesamten Status des Objekts zu erstellen.


3
Dies wird so dargestellt, als ob Mutabilität und Änderungen nach der Instanziierung die einzige Möglichkeit sind, mehrere Werte festzulegen. Der Vollständigkeit halber sei angemerkt, dass das Builder-Muster die gleichen oder noch leistungsstärkeren Funktionen bietet und keine benötigt, um die Unveränderlichkeit zu opfern. new ReportBuilder().font("Arial").orientation("landscape").build()
gnat

1
"Es wäre nicht praktisch, eine sehr lange Konstruktorsignatur zu haben.": Sie können Parameter immer in kleinere Objekte gruppieren und diese Objekte als Parameter an den Konstruktor übergeben.
Giorgio

1
@cHao Was ist, wenn Sie 10 oder 15 Attribute festlegen möchten? Es scheint auch nicht gut zu sein, dass eine Methode namens a withFontzurückgibt Report.
Tulains Córdova

1
Was das Setzen von 10 oder 15 Attributen angeht, wäre der Code dafür nicht umständlich, wenn (1) Java die Konstruktion all dieser Zwischenobjekte zu umgehen wüsste, und wenn (2) die Namen wiederum standardisiert wären. Es ist kein Problem mit der Unveränderlichkeit; Es ist ein Problem mit Java, das nicht weiß, wie man es richtig macht.
cHao

1
@cHao Eine Aufrufkette für 20 Attribute zu erstellen ist hässlich. Hässlicher Code ist in der Regel ein Code mit schlechter Qualität.
Tulains Córdova

1

Ich denke, die Verwendung von veränderlichen Objekten ergibt sich aus dem imperativen Denken: Sie berechnen ein Ergebnis, indem Sie den Inhalt von veränderlichen Variablen schrittweise ändern (Berechnung nach Nebeneffekt).

Wenn Sie funktional denken, möchten Sie einen unveränderlichen Zustand haben und nachfolgende Zustände eines Systems darstellen, indem Sie Funktionen anwenden und aus alten Werten neue Werte erstellen.

Der funktionale Ansatz kann sauberer und robuster sein, er kann jedoch aufgrund des Kopierens sehr ineffizient sein, sodass Sie auf eine gemeinsam genutzte Datenstruktur zurückgreifen möchten, die Sie schrittweise ändern.

Der für mich vernünftigste Kompromiss ist: Beginnen Sie mit unveränderlichen Objekten und wechseln Sie dann zu veränderlichen Objekten, wenn Ihre Implementierung nicht schnell genug ist. Unter diesem Gesichtspunkt kann die systematische Verwendung von veränderlichen Objekten von Anfang an als vorzeitige Optimierung angesehen werden : Sie wählen von Anfang an die effizientere (aber auch schwieriger zu verstehende und zu debuggende) Implementierung.

Warum verwenden viele Programmierer veränderbare Objekte? IMHO aus zwei Gründen:

  1. Viele Programmierer haben gelernt, mit einem imperativen (prozeduralen oder objektorientierten) Paradigma zu programmieren. Daher ist Mutabilität ihr grundlegender Ansatz zur Definition von Berechnung, dh sie wissen nicht, wann und wie sie Unveränderlichkeit verwenden sollen, weil sie nicht mit ihr vertraut sind.
  2. Viele Programmierer sorgen sich zu früh um die Leistung, während es oft effektiver ist, sich zuerst auf das Schreiben eines funktional korrekten Programms zu konzentrieren und dann zu versuchen, Engpässe zu finden und es zu optimieren.

1
Das Problem ist nicht nur die Geschwindigkeit. Es gibt viele Arten nützlicher Konstrukte, die ohne die Verwendung veränderlicher Typen einfach nicht implementiert werden können. Unter anderem erfordert die Kommunikation zwischen zwei Threads, dass beide mindestens einen Verweis auf ein gemeinsam genutztes Objekt haben, das von einem Datenelement abgelegt und vom anderen gelesen werden kann. Ferner ist es oft semantisch viel klarer zu sagen "Ändere Eigenschaften P und Q dieses Objekts" als zu sagen "Nimm dieses Objekt, konstruiere ein neues Objekt, das genau so ist wie es, außer dem Wert von P, und dann ein neues Objekt, welches ist einfach so mit Ausnahme von Q ".
Supercat

1
Ich bin kein FP-Experte, aber die AFAIK (1) -Thread-Kommunikation kann erreicht werden, indem in einem Thread ein unveränderlicher Wert erstellt und in einem anderen gelesen wird (AFAIK, dies ist der Erlang-Ansatz). (2) AFAIK, die Notation zum Festlegen einer Eigenschaft ändert sich nicht viel (z. B. in Haskell entspricht der Wert des setProperty-Datensatzes dem Wert von Java record.setProperty (value)), nur die Semantik ändert sich, da das Ergebnis des Festlegens einer Eigenschaft ein neuer unveränderlicher Datensatz ist.
Giorgio

1
"beide müssen einen Verweis auf ein gemeinsam genutztes Objekt haben, das einer zum Speichern von Daten und der andere zum Lesen von Daten verwenden kann": Genauer gesagt, Sie können ein Objekt unveränderlich machen (alle Member const in C ++ oder final in Java) und den gesamten Inhalt in festlegen Konstrukteur. Dann wird dieses unveränderliche Objekt vom Producer-Thread an den Consumer-Thread übergeben.
Giorgio

1
(2) Ich habe die Leistung bereits als Grund aufgeführt, den unveränderlichen Zustand nicht zu verwenden. In Bezug auf (1) benötigt der Mechanismus, der die Kommunikation zwischen Threads implementiert, natürlich einen veränderlichen Status, aber Sie können dies vor Ihrem Programmiermodell verbergen, siehe z. B. das Akteurmodell ( en.wikipedia.org/wiki/Actor_model ). Selbst wenn Sie auf einer niedrigeren Ebene eine Veränderlichkeit benötigen, um die Kommunikation zu implementieren, können Sie eine höhere Abstraktionsebene haben, in der Sie zwischen Threads kommunizieren, die unveränderliche Objekte hin und her senden.
Giorgio

1
Ich bin mir nicht sicher, ob ich Sie vollständig verstehe, aber selbst in einer reinen funktionalen Sprache, in der jeder Wert unveränderlich ist, gibt es eine veränderbare Sache: den Programmstatus, dh den aktuellen Programmstapel und die Variablenbindungen. Dies ist jedoch die Ausführungsumgebung. Wie auch immer, ich schlage vor, dass wir dies einige Zeit im Chat diskutieren und dann die Nachrichten von dieser Frage bereinigen.
Giorgio

1

Ich weiß, dass Sie nach Java fragen, aber in Objective-C verwende ich ständig veränderliche vs. unveränderliche. Es gibt ein unveränderliches Array NSArray und ein veränderliches Array NSMutableArray. Hierbei handelt es sich um zwei verschiedene Klassen, die speziell für die genaue Verwendung optimiert wurden. Wenn ich ein Array erstellen und dessen Inhalt niemals ändern muss, verwende ich NSArray, ein kleineres Objekt, das im Vergleich zu einem veränderlichen Array viel schneller ist.

Wenn Sie also ein unveränderliches Person-Objekt erstellen, benötigen Sie nur einen Konstruktor und Getter. Dadurch wird das Objekt kleiner und benötigt weniger Speicher, wodurch Ihr Programm tatsächlich schneller wird. Wenn Sie das Objekt nach der Erstellung ändern müssen, ist ein veränderbares Personenobjekt besser geeignet, damit die Werte geändert werden können, anstatt ein neues Objekt zu erstellen.

Also: Je nachdem, was Sie mit dem Objekt vorhaben, kann die Wahl von „wandelbar“ oder „unveränderlich“ einen großen Unterschied in Bezug auf die Leistung bedeuten.


1

Zusätzlich zu vielen anderen hier angeführten Gründen besteht ein Problem darin, dass die Hauptsprachen die Unveränderlichkeit nicht gut unterstützen. Zumindest werden Sie für die Unveränderlichkeit dadurch bestraft, dass Sie zusätzliche Schlüsselwörter wie const oder final hinzufügen und unleserliche Konstruktoren mit vielen, vielen Argumenten oder codelangen Buildermustern verwenden müssen.

Das ist viel einfacher in Sprachen, die auf Unveränderlichkeit ausgelegt sind. Betrachten Sie dieses Scala-Snippet zum Definieren einer Klasse Person, optional mit benannten Argumenten, und zum Erstellen einer Kopie mit einem geänderten Attribut:

case class Person(id: String, firstName: String, lastName: String)

val joe = Person("123", "Joe", "Doe")
val spouse = Person(id = "124", firstName = "Mary", lastName = "Moe")
val joeMarried = joe.copy(lastName = "Doe-Moe")

Wenn Sie also möchten, dass Entwickler die Unveränderlichkeit übernehmen, ist dies einer der Gründe, warum Sie möglicherweise in Erwägung ziehen, auf eine modernere Programmiersprache zu wechseln.


dies scheint nicht wesentlich etwas hinzuzufügen über Punkte gemacht und erläuterten vor 24 Antworten
gnat

@gnat Welche der vorherigen Antworten weist darauf hin, dass die meisten gängigen Sprachen die Unveränderlichkeit nicht angemessen unterstützen? Ich denke, dieser Punkt wurde einfach nicht angesprochen (ich habe es überprüft), aber dies ist meiner Meinung nach ein ziemlich wichtiges Hindernis.
Hans-Peter Störr

In diesem Beispiel wird dieses Problem in Java ausführlich erläutert. Und mindestens 3 weitere Antworten indirekt auf sie beziehen
gnat

0

Unveränderlich bedeutet, dass Sie den Wert nicht ändern können, und veränderlich bedeutet, dass Sie den Wert ändern können, wenn Sie in Primitiven und Objekten denken. Objekte unterscheiden sich von Grundelementen in Java darin, dass sie in Typen erstellt werden. Primitive werden in Typen wie int, boolean und void erstellt.

Viele Leute denken, dass Primitive und Objektvariablen, denen ein abschließender Modifikator vorangestellt ist, unveränderlich sind. Dies ist jedoch nicht genau der Fall. Final bedeutet also fast nicht unveränderlich für Variablen. Schauen Sie sich diesen Link für ein Codebeispiel an:

public abstract class FinalBase {

    private final int variable; // Unset

    /* if final really means immutable than
     * I shouldn't be able to set the variable
     * but I can.
     */
    public FinalBase(int variable) { 
        this.variable = variable;
    }

    public int getVariable() {
        return variable;
    }

    public abstract void method();
}

// This is not fully necessary for this example
// but helps you see how to set the final value 
// in a sub class.
public class FinalSubclass extends FinalBase {

    public FinalSubclass(int variable) {
        super(variable);
    }

    @Override
    public void method() {
        System.out.println( getVariable() );
    }

    @Override
    public int getVariable() {

        return super.getVariable();
    }

    public static void main(String[] args) {
        FinalSubclass subclass = new FinalSubclass(10);
        subclass.method();
    }
}

-1

Ich denke, ein guter Grund wäre, "echte" veränderbare "Objekte" wie Interface-Fenster zu modellieren. Ich erinnere mich vage, dass ich gelesen hatte, dass das OOP erfunden wurde, als jemand versuchte, Software zur Steuerung eines Frachthafenbetriebs zu schreiben.


1
Ich kann noch nicht finden, wo ich dies über die Ursprünge von OOP gelesen habe, aber laut Wikipedia ist ein integriertes regionales Informationssystem einer großen Containerschifffahrtsgesellschaft OOCL in Smalltalk geschrieben.
Alexey

-2

Java erfordert in vielen Fällen veränderbare Objekte, z. B. wenn Sie einen Besucher oder eine ausführbare Datei zählen oder zurückgeben möchten, benötigen Sie endgültige Variablen, auch ohne Multithreading.


5
finalist eigentlich etwa ein Viertel eines Schrittes in Richtung Unveränderlichkeit. Nicht zu sehen, warum Sie es erwähnen.
CHAO

Hast du meinen Beitrag tatsächlich gelesen?
Ralf H

Hast du die Frage tatsächlich gelesen? :) Es hatte absolut nichts damit zu tun final. Wenn man es in diesem Zusammenhang aufbringt, entsteht eine merkwürdige Verschmelzung von final"veränderlich", wenn finales darum geht , bestimmte Arten von Mutationen zu verhindern . (Übrigens nicht meine Ablehnung.)
cHao

Ich sage nicht, dass Sie hier irgendwo keinen gültigen Punkt haben. Ich sage, Sie erklären es nicht sehr gut. Ich sehe halbwegs, wohin Sie wahrscheinlich gehen, aber Sie müssen noch weiter gehen. So wie es ist, sieht es nur verwirrt aus.
CHAO

1
Es gibt tatsächlich sehr wenige Fälle, in denen ein veränderbares Objekt erforderlich ist . Es gibt Fälle, in denen der Code durch die Verwendung veränderlicher Objekte klarer oder in einigen Fällen schneller ist. Bedenken Sie jedoch, dass die überwiegende Mehrheit der Entwurfsmuster im Grunde genommen nur eine halbherzige Wiedergabe der funktionalen Programmierung für Sprachen ist, die diese nicht von Haus aus unterstützen. Ein lustiger Teil davon ist, dass FP nur an sehr ausgewählten Stellen (d. H. Dort, wo Nebenwirkungen auftreten müssen) Veränderlichkeit erfordert und diese Stellen in Anzahl, Größe und Umfang im Allgemeinen stark eingeschränkt sind.
cHao
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.