Ich sehe die Vorteile, Objekte in meinem Programm unveränderlich zu machen. Wenn ich wirklich tief über ein gutes Design für meine Anwendung nachdenke, komme ich natürlich oft zu dem Ergebnis, dass viele meiner Objekte unveränderlich sind. Es kommt oft zu einem Punkt, an dem ich alle meine Objekte unveränderlich haben möchte .
Diese Frage befasst sich mit der gleichen Idee, aber keine Antwort legt nahe, was ein guter Ansatz für die Unveränderlichkeit ist und wann sie tatsächlich verwendet werden muss. Gibt es gute unveränderliche Designmuster? Die allgemeine Idee scheint zu sein, "Objekte unveränderlich zu machen, es sei denn, Sie müssen sie unbedingt ändern", was in der Praxis nutzlos ist.
Ich habe die Erfahrung gemacht, dass Unveränderlichkeit meinen Code immer mehr zum funktionalen Paradigma treibt und dieser Fortschritt immer passiert:
- Ich brauche beständige (im funktionalen Sinne) Datenstrukturen wie Listen, Karten usw.
- Es ist äußerst unpraktisch, mit Querverweisen zu arbeiten (z. B. Baumknoten, die auf seine Kinder verweisen, während Kinder auf ihre Eltern verweisen), wodurch ich überhaupt keine Querverweise verwende, was wiederum meine Datenstrukturen und meinen Code funktionaler macht.
- Die Vererbung hört auf, irgendeinen Sinn zu ergeben, und ich beginne stattdessen, Komposition zu verwenden.
- Die gesamten Grundideen von OOP wie die Kapselung fallen auseinander und meine Objekte sehen aus wie Funktionen.
Zu diesem Zeitpunkt verwende ich praktisch nichts mehr aus dem OOP-Paradigma und kann einfach zu einer rein funktionalen Sprache wechseln. Daher meine Frage: Gibt es einen konsistenten Ansatz für ein gutes unveränderliches OOP-Design oder ist es immer so, dass Sie, wenn Sie die unveränderliche Idee voll ausschöpfen, immer in einer funktionalen Sprache programmieren, die nichts mehr aus der OOP-Welt benötigt? Gibt es gute Richtlinien, um zu entscheiden, welche Klassen unveränderlich sein sollen und welche veränderlich bleiben sollen, um sicherzustellen, dass OOP nicht auseinander fällt?
Nur der Einfachheit halber werde ich ein Beispiel geben. Lassen Sie uns ChessBoard
eine unveränderliche Sammlung unveränderlicher Schachfiguren haben (Erweiterung der abstrakten KlassePiece
). Aus der Sicht der OOP ist ein Stück dafür verantwortlich, gültige Bewegungen aus seiner Position auf dem Brett zu generieren. Aber um die Bewegungen zu erzeugen, benötigt das Stück einen Verweis auf sein Brett, während das Brett einen Verweis auf seine Stücke haben muss. Nun, es gibt einige Tricks, um diese unveränderlichen Querverweise abhängig von Ihrer OOP-Sprache zu erstellen, aber sie sind mühsam zu verwalten, besser kein Stück, um auf das Board zu verweisen. Aber dann kann das Stück seine Bewegungen nicht erzeugen, da es den Zustand des Bretts nicht kennt. Dann wird das Stück nur eine Datenstruktur, die den Stücktyp und seine Position enthält. Sie können dann eine polymorphe Funktion verwenden, um Bewegungen für alle Arten von Teilen zu generieren. Dies ist in der funktionalen Programmierung perfekt erreichbar, in OOP jedoch fast unmöglich, ohne Laufzeit-Typprüfungen und andere schlechte OOP-Praktiken ... Dann