Ich komme aus einem OOP-Hintergrund (Java) und lerne Scala alleine. Ich kann die Vorteile der Verwendung unveränderlicher Objekte zwar leicht erkennen, aber es fällt mir schwer, zu sehen, wie man eine ganze Anwendung so gestalten kann. Ich gebe ein Beispiel:
Angenommen, ich habe Objekte, die "Materialien" und ihre Eigenschaften darstellen (ich entwerfe ein Spiel, also habe ich dieses Problem wirklich), wie Wasser und Eis. Ich hätte einen "Manager", der alle derartigen Materialinstanzen besitzt. Eine Eigenschaft wäre der Gefrier- und Schmelzpunkt und was das Material einfriert oder schmilzt.
[BEARBEITEN] Alle materiellen Instanzen sind "Singleton", ähnlich einer Java-Enumeration.
Ich möchte, dass "Wasser" sagt, dass es bei 0 ° C zu "Eis" gefriert und "Eis" sagt, dass es bei 1 ° C zu "Wasser" schmilzt. Wenn jedoch Wasser und Eis unveränderlich sind, können sie nicht als Konstruktorparameter aufeinander verweisen, da einer von ihnen zuerst erstellt werden muss und der andere nicht als Konstruktorparameter auf den noch nicht existierenden anderen verweisen kann. Ich könnte das lösen, indem ich beiden einen Verweis auf den Manager gebe, damit sie ihn abfragen können, um die andere Materialinstanz zu finden, die sie jedes Mal benötigen, wenn sie nach ihren Gefrier- / Schmelzeigenschaften gefragt werden, aber dann habe ich das gleiche Problem zwischen den Managern und die Materialien, die einen Bezug zueinander benötigen, aber nur für eines von ihnen im Konstruktor bereitgestellt werden können, sodass weder der Manager noch das Material unveränderlich sein können.
Kann man dieses Problem nicht umgehen, oder muss ich "funktionale" Programmiertechniken oder ein anderes Muster verwenden, um es zu lösen?
quelle
h2o
MaterialAntworten:
Die Lösung ist, ein bisschen zu schummeln. Speziell:
Erstellen Sie A, aber lassen Sie den Verweis auf B uninitialisiert (da B noch nicht existiert).
Erstellen Sie B und lassen Sie es auf A zeigen.
Aktualisieren Sie A, um auf B zu zeigen. Aktualisieren Sie danach weder A noch B.
Dies kann entweder explizit erfolgen (Beispiel in C ++):
oder implizit (Beispiel in Haskell):
Das Beispiel von Haskell verwendet eine verzögerte Auswertung, um die Illusion von voneinander abhängigen unveränderlichen Werten zu erreichen. Die Werte beginnen mit:
a
undb
sind beide unabhängig voneinander gültige Kopfnormalformen . Die einzelnen Nachteile können konstruiert werden, ohne den Endwert der anderen Variablen zu benötigen. Wenn der Thunk ausgewertet wird, zeigt er auf dieselben Datenpunkteb
.Wenn Sie also möchten, dass zwei unveränderliche Werte aufeinander verweisen, müssen Sie entweder den ersten nach dem Erstellen des zweiten aktualisieren oder einen Mechanismus auf höherer Ebene verwenden, um dasselbe zu tun.
In Ihrem speziellen Beispiel könnte ich es in Haskell so ausdrücken:
Ich gehe jedoch aus dem Ruder. Ich würde mir vorstellen, dass bei einem objektorientierten Ansatz, bei dem eine
setTemperature
Methode an das Ergebnis jedesMaterial
Konstruktors angehängt wird, die Konstruktoren aufeinander zeigen müssen. Wenn die Konstruktoren als unveränderliche Werte behandelt werden, können Sie den oben beschriebenen Ansatz verwenden.quelle
In Ihrem Beispiel wenden Sie eine Transformation auf ein Objekt an, sodass ich so etwas wie eine
ApplyTransform()
Methode verwenden würde, die a zurückgibt,BlockBase
anstatt zu versuchen, das aktuelle Objekt zu ändern.Wenn Sie zum Beispiel einen IceBlock durch Anwenden von Wärme in einen WaterBlock umwandeln, würde ich so etwas nennen
und die
IceBlock.ApplyTemperature()
Methode würde ungefähr so aussehen:quelle
BlockList.WaterBlock
anstatt einen neuen Block zu erstellen?BlockList
nur einestatic
Klasse ist, die für die einzelnen Instanzen jedes Blocks verantwortlich ist, sodass Sie keine Instanz von erstellen müssenBlockList
(ich bin an C #Eine andere Möglichkeit, den Kreislauf zu durchbrechen, besteht darin, die Anliegen von Material und Transmutation in einer bestimmten Sprache zu trennen:
quelle
Wenn Sie eine funktionale Sprache verwenden und die Vorteile der Unveränderlichkeit nutzen möchten, sollten Sie das Problem unter Berücksichtigung dieser Aspekte angehen. Sie versuchen, einen Objekttyp "Eis" oder "Wasser" zu definieren, der einen Temperaturbereich unterstützen kann. Um die Unveränderlichkeit zu unterstützen, müssen Sie dann jedes Mal ein neues Objekt erstellen, wenn sich die Temperatur ändert. Dies ist verschwenderisch. Versuchen Sie daher, die Konzepte von Blocktyp und Temperatur unabhängiger zu machen. Ich kenne Scala nicht (es steht auf meiner To-Learn-Liste :-)), leihe mir aber Joey Adams aus. Antworte in Haskell , ich schlage Folgendes vor:
oder vielleicht:
(Hinweis: Ich habe nicht versucht, dies auszuführen, und mein Haskell ist ein wenig verrostet.) Nun ist die Übergangslogik vom Materialtyp getrennt, sodass weniger Speicher verschwendet wird, und (meiner Meinung nach) ist es ziemlich ein bisschen mehr funktional ausgerichtet.
quelle