In den meisten OOP-Sprachen sind Objekte im Allgemeinen mit einer begrenzten Anzahl von Ausnahmen (wie z. B. Tupel und Zeichenfolgen in Python) veränderbar. In den meisten funktionalen Sprachen sind Daten unveränderlich.
Sowohl veränderbare als auch unveränderliche Objekte bringen eine ganze Reihe von Vor- und Nachteilen mit sich.
Es gibt Sprachen, die versuchen, beide Konzepte zu verbinden, wie z. B. Scala, bei denen Sie (explizit deklarierte) veränderbare und unveränderbare Daten haben (bitte korrigieren Sie mich, wenn ich mich irre, meine Kenntnisse über Scala sind mehr als begrenzt).
Meine Frage ist: Ist vollständige (sic!) Unveränderlichkeit - dh, kein Objekt kann sich ändern, wenn es einmal erstellt wurde - in einem OOP-Kontext sinnvoll?
Gibt es Entwürfe oder Implementierungen eines solchen Modells?
Sind (vollständige) Unveränderlichkeit und OOP grundsätzlich gegensätzlich oder orthogonal?
Motivation: In OOP Sie normal arbeiten auf Daten, Ändern (mutiert) , um die zugrunde liegenden Informationen, Referenzen zwischen den Objekten zu halten. ZB ein Klassenobjekt Person
mit einem Mitglied, das father
auf ein anderes Person
Objekt verweist . Wenn Sie den Namen des Vaters ändern, ist dies für das untergeordnete Objekt sofort sichtbar, ohne dass eine Aktualisierung erforderlich ist. Da Sie unveränderlich sind, müssen Sie neue Objekte für Vater und Kind konstruieren. Mit gemeinsamen Objekten, Multithreading, GIL usw. hätten Sie jedoch viel weniger Probleme.
quelle
Antworten:
OOP und Unveränderlichkeit sind fast vollständig orthogonal zueinander. Imperative Programmierung und Unveränderlichkeit sind es jedoch nicht.
OOP kann durch zwei Kernfunktionen zusammengefasst werden:
Kapselung : Ich greife nicht direkt auf den Inhalt von Objekten zu, sondern kommuniziere über eine bestimmte Schnittstelle („Methoden“) mit diesem Objekt. Diese Schnittstelle kann interne Daten vor mir verbergen. Technisch gesehen bezieht sich dies eher auf die modulare Programmierung als auf OOP. Der Zugriff auf Daten über eine definierte Schnittstelle entspricht in etwa einem abstrakten Datentyp.
Dynamischer Versand : Wenn ich eine Methode für ein Objekt aufrufe, wird die ausgeführte Methode zur Laufzeit aufgelöst. (In klassenbasiertem OOP kann ich beispielsweise eine
size
Methode für eineIList
Instanz aufrufen, der Aufruf wird jedoch möglicherweise in eine Implementierung in einerLinkedList
Klasse aufgelöst.) Dynamischer Versand ist eine Möglichkeit, polymorphes Verhalten zuzulassen.Kapselung ist ohne Veränderbarkeit weniger sinnvoll (es gibt keinen internen Zustand , der durch Einmischung von außen verfälscht werden könnte), aber sie macht Abstraktionen auch dann leichter, wenn alles unveränderlich ist.
Ein imperatives Programm besteht aus Anweisungen, die nacheinander ausgeführt werden. Eine Anweisung hat Nebenwirkungen wie das Ändern des Programmstatus. Bei Unveränderlichkeit kann der Status nicht geändert werden (natürlich kann ein neuer Status erstellt werden). Imperative Programmierung ist daher grundsätzlich nicht mit Unveränderlichkeit vereinbar.
Es kommt nun vor, dass OOP historisch immer mit imperativer Programmierung verbunden war (Simula basiert auf Algol) und alle gängigen OOP-Sprachen imperative Wurzeln haben (C ++, Java, C #, ... sind alle in C verwurzelt). Dies bedeutet nicht, dass OOP selbst zwingend oder veränderlich wäre. Dies bedeutet lediglich, dass die Implementierung von OOP durch diese Sprachen eine Veränderlichkeit ermöglicht.
quelle
Beachten Sie, dass es unter objektorientierten Programmierern eine Kultur gibt, in der die Leute davon ausgehen, dass die meisten Ihrer Objekte veränderbar sind, wenn Sie OOP ausführen. Dies ist jedoch eine andere Frage als die, ob OOP eine Veränderlichkeit erfordert . Außerdem scheint sich diese Kultur langsam zu mehr Unveränderlichkeit zu verändern, da die Menschen der funktionalen Programmierung ausgesetzt sind.
Scala ist ein wirklich gutes Beispiel dafür, dass für die Objektorientierung keine Veränderlichkeit erforderlich ist. Während Scala die Wandlungsfähigkeit unterstützt , wird von seiner Verwendung abgeraten. Idiomatic Scala ist sehr objektorientiert und nahezu unveränderlich. Es ermöglicht hauptsächlich die Veränderbarkeit für die Kompatibilität mit Java, und weil unter bestimmten Umständen unveränderliche Objekte ineffizient oder verschlungen sind, um damit zu arbeiten.
Vergleichen Sie beispielsweise eine Scala-Liste und eine Java-Liste . Die unveränderliche Liste von Scala enthält alle die gleichen Objektmethoden wie die veränderbare Liste von Java. Tatsächlich, weil Java statische Funktionen für Operationen wie sort verwendet und Scala Methoden im funktionalen Stil wie
map
. Alle Kennzeichen von OOP - Kapselung, Vererbung und Polymorphismus - sind in einer Form verfügbar, die objektorientierten Programmierern vertraut ist und in geeigneter Weise verwendet wird.Der einzige Unterschied besteht darin, dass Sie beim Ändern der Liste ein neues Objekt erhalten. Das erfordert oft, dass Sie andere Entwurfsmuster verwenden als bei veränderlichen Objekten, aber Sie müssen OOP nicht ganz aufgeben.
quelle
Unveränderlichkeit kann in einer OOP-Sprache simuliert werden, indem nur Objektzugriffspunkte als Methoden oder schreibgeschützte Eigenschaften verfügbar gemacht werden, die die Daten nicht verändern. Die Unveränderlichkeit funktioniert in OOP-Sprachen genauso wie in jeder anderen funktionalen Sprache, mit der Ausnahme, dass möglicherweise einige Funktionen der funktionalen Sprache fehlen.
Ihre Vermutung scheint zu sein, dass die Wandlungsfähigkeit ein Kernmerkmal der Objektorientierung ist. Aber Veränderlichkeit ist einfach eine Eigenschaft von Objekten oder Werten. Die Objektorientierung umfasst eine Reihe von Begriffen (Verkapselung, Polymorphismus, Vererbung usw.), die wenig oder gar nichts mit Mutation zu tun haben, und Sie würden trotzdem die Vorteile dieser Funktionen nutzen, selbst wenn Sie alles unveränderlich machen würden.
Auch müssen nicht alle funktionalen Sprachen unveränderlich sein. Clojure verfügt über eine spezielle Annotation, mit der Typen geändert werden können, und die meisten "praktischen" funktionalen Sprachen können veränderbare Typen angeben.
Eine bessere Frage könnte sein: "Ist vollständige Unveränderlichkeit in der imperativen Programmierung sinnvoll ?" Ich würde sagen, die offensichtliche Antwort auf diese Frage ist nein. Um eine vollständige Unveränderlichkeit in der imperativen Programmierung zu erreichen, müssten Sie auf
for
Schleifen verzichten (da Sie eine Schleifenvariable mutieren müssten), und zwar zugunsten der Rekursion, und jetzt programmieren Sie im Wesentlichen sowieso funktional.quelle
Es ist oft nützlich, Objekte als Verkapselung von Werten oder Entitäten zu kategorisieren. Der Unterschied besteht darin, dass Code, der einen Verweis auf einen Wert enthält, niemals dessen Status in einer Weise ändern sollte, die der Code selbst nicht initiiert hat. Im Gegensatz dazu kann Code, der einen Verweis auf ein Unternehmen enthält, erwarten, dass er sich außerhalb der Kontrolle des Referenzinhabers ändert.
Während es möglich ist, einen Kapselungswert mit Objekten veränderlichen oder unveränderlichen Typs zu verwenden, kann sich ein Objekt nur als Wert verhalten, wenn mindestens eine der folgenden Bedingungen zutrifft:
Kein Verweis auf das Objekt wird jemals einem Gegenstand ausgesetzt, der den darin eingeschlossenen Zustand verändern könnte.
Der Inhaber von mindestens einem der Verweise auf das Objekt kennt alle Verwendungen, für die ein noch vorhandener Verweis verwendet werden könnte.
Da alle Instanzen unveränderlicher Typen automatisch die erste Anforderung erfüllen, ist es einfach, sie als Werte zu verwenden. Im Gegensatz dazu ist es viel schwieriger, sicherzustellen, dass beide Anforderungen erfüllt sind, wenn veränderbare Typen verwendet werden. Während Verweise auf unveränderliche Typen als Mittel zum Einkapseln des darin eingekapselten Zustands frei weitergegeben werden können, erfordert das Umgehen des in veränderlichen Typen gespeicherten Zustands entweder das Erstellen unveränderlicher Wrapper-Objekte oder das Kopieren des durch privat gehaltene Objekte eingekapselten Zustands in andere Objekte Entweder geliefert von oder konstruiert für den Empfänger der Daten.
Unveränderliche Typen eignen sich sehr gut zum Übergeben von Werten und sind häufig zumindest in gewissem Umfang zum Manipulieren von Werten geeignet. Sie sind jedoch nicht so gut im Umgang mit Entitäten. Das nächste, was man zu einer Entität in einem System mit rein unveränderlichen Typen haben kann, ist eine Funktion, die in Anbetracht des Systemzustands die Attribute eines Teils davon meldet oder eine neue Systemzustandsinstanz erzeugt, die a ähnelt geliefert, mit Ausnahme eines bestimmten Teils davon, der in wählbarer Weise unterschiedlich sein wird. Wenn der Zweck einer Entität darin besteht, einen Code mit etwas zu verknüpfen, das in der realen Welt existiert, kann es für die Entität möglicherweise unmöglich sein, das Offenlegen eines veränderlichen Zustands zu vermeiden.
Wenn man beispielsweise einige Daten über eine TCP-Verbindung empfängt, könnte man ein neues Objekt "Zustand der Welt" erzeugen, das diese Daten in seinem Puffer enthält, ohne dass sich dies auf Verweise auf den alten "Zustand der Welt", aber auf alte Kopien von auswirkt Der Weltzustand, der den letzten Datenstapel nicht enthält, ist fehlerhaft und sollte nicht verwendet werden, da er nicht mehr mit dem Zustand des realen TCP-Sockets übereinstimmt.
quelle
In c # sind einige Typen unveränderlich wie Zeichenfolgen.
Dies scheint weiterhin darauf hinzudeuten, dass die Wahl stark in Betracht gezogen wurde.
Natürlich ist es sehr leistungsintensiv, unveränderliche Typen zu verwenden, wenn Sie diesen Typ hunderttausend Mal ändern müssen. Aus diesem Grund wird in diesem Fall empfohlen, die
StringBuilder
Klasse anstelle derstring
Klasse zu verwenden.Ich habe ein Experiment mit einem Profiler gemacht und die Verwendung des unveränderlichen Typs ist wirklich mehr CPU- und RAM-Anforderungen.
Es ist auch intuitiv, wenn Sie bedenken, dass Sie zum Ändern von nur einem Buchstaben in einer Zeichenfolge von 4000 Zeichen jedes Zeichen in einen anderen Bereich des RAM kopieren müssen.
quelle
string
Verkettung katastrophal langsam sein . Für nahezu alle Arten von Daten / Anwendungsfällen kann (oftmals bereits) eine effiziente persistente Struktur erfunden werden. Die meisten von ihnen haben ungefähr die gleiche Leistung, auch wenn die konstanten Faktoren manchmal schlechter sind.string
(der traditionellen Darstellung). Ein "String" (in der Darstellung, von der ich spreche) nach 1000 Modifikationen wäre wie ein frisch erstellter String (Moduloinhalt); Keine nützliche oder weit verbreitete persistente Datenstruktur verschlechtert die Qualität nach X-Operationen. Speicherfragmentierung ist kein ernstes Problem (Sie würden viele Zuweisungen haben, ja, aber Fragmentierung ist in modernen Garbage Collectors kein Problem )Vollständige Unveränderlichkeit von allem ergibt in OOP oder den meisten anderen Paradigmen aus einem sehr wichtigen Grund keinen Sinn:
Jedes nützliche Programm hat Nebenwirkungen.
Ein Programm, das nichts verändert, ist wertlos. Möglicherweise haben Sie es nicht einmal ausgeführt, da der Effekt identisch ist.
Auch wenn Sie denken, dass Sie nichts ändern, und einfach eine Liste von Zahlen zusammenfassen, die Sie irgendwie erhalten haben, denken Sie daran, dass Sie etwas mit dem Ergebnis anfangen müssen - ob Sie es auf Standardausgabe drucken, in eine Datei schreiben, oder wo auch immer. Dazu muss ein Puffer mutiert und der Status des Systems geändert werden.
Es kann sehr sinnvoll sein, die Veränderbarkeit auf die Teile zu beschränken , die geändert werden müssen. Aber wenn sich absolut nichts ändern muss, dann tun Sie nichts, was es wert ist, getan zu werden.
quelle
Ich denke, es hängt davon ab, ob Ihre Definition von OOP darin besteht, dass ein Nachrichtenübermittlungsstil verwendet wird.
Reine Funktionen müssen nichts ändern, da sie Werte zurückgeben, die Sie in neuen Variablen speichern können.
Mit dem Nachrichtenübergabestil weisen Sie ein Objekt an, neue Daten zu speichern, anstatt es zu fragen, welche neuen Daten in einer neuen Variablen gespeichert werden sollen.
Es ist möglich, Objekte zu haben und sie nicht zu mutieren, indem man ihre Methoden zu reinen Funktionen macht, die zufällig im Inneren des Objekts statt außerhalb leben.
Es ist jedoch nicht möglich, den Stil der Nachrichtenübermittlung mit unveränderlichen Objekten zu mischen.
quelle