解释问题:
是否可以在不可变集合中的一个对象发生变化时自动更新所有相关对象?
简答
没有。
长答案
没有,但在不可变数据结构中没有任何变化,所以这不是问题。
更长的答案
这更复杂......
不变性
不可变对象的全部意义在于,如果您有对不可变对象的引用,则不必费心检查其任何属性是否已更改。那么,这不是问题吗?嗯……
后果
这会产生一些后果 - 它们是好是坏取决于您的期望:
但世界是可变的
当然,在实践中,我们经常处理随时间变化的值。不可变状态似乎无法描述可变世界,但人们有一些处理方式。
这样看:如果您在某个 ID 上有您的地址并且您搬到了另一个地址,则应该更改该 ID 以与新的真实数据保持一致,因为您居住在该地址不再是真实的.但是,当您购买包含您的地址的东西时收到发票,然后您更改了地址,则发票保持不变,因为您在开具发票时居住在该地址仍然是真实的。现实世界中的一些数据表示是不可变的,例如该示例中的发票,而有些则是可变的,例如 ID。
现在以您的示例为例,如果您选择使用不可变结构来对数据建模,则必须以您考虑我示例中的发票的方式来考虑它。数据可能不是最新的,但它在某个时间点始终是一致和真实的,并且永远不会改变。
如何应对变化
那么如何使用不可变数据对变化进行建模?在Clojure 中使用Vars、Refs、Atoms 和Agents 和ClojureScript(针对JavaScript 的Clojure 编译器)有一个很好的解决方法)支持其中一些(在特定的 Atom 应该像在 Clojure 中一样工作,但没有 Refs、STM、Vars 或 Agent - 请参阅 what are the differences between ClojureScript and Clojure regarding concurrency features)。
看how Atoms are implemented in ClojureScript 似乎你可以使用普通的JavaScript 对象来实现相同的功能。它适用于诸如拥有一个本身可变的 JavaScript 对象,但它具有一个对不可变对象的引用的属性 - 您将无法更改该不可变对象的任何属性,但您将能够构造一个不同的不可变对象,并将旧对象交换为顶级可变对象中的新对象。
像Haskell 和purely functional 这样的其他语言可能有不同的方式来处理可变世界,比如monads(一个众所周知的难以解释的概念-JavaScript: The Good Parts 和discoverer of JSON 的作者Douglas Crockford 将其归因于它在他的演讲中提到“一元诅咒”Monads and Gonads)。
你的问题看起来很简单,但它所涉及的问题实际上相当复杂。当然,对您的问题是否可以在一个对象发生更改时自动更新所有相关对象时回答“否”会失去意义,但它比这更复杂,并且说在不可变对象中什么都不会改变(所以这个问题永远不会发生)同样没有帮助。
可能的解决方案
您可以拥有一个顶级对象或变量,您始终可以从中访问所有结构。假设您有:
var data = { value: Immutable.Map({...}) }
如果您总是使用data.value(或使用更好的名称)访问您的数据,那么您可以将data 传递给代码的其他部分,并且每当您的状态发生变化时,您只需分配一个新的 Immutable.Map ({...}) 到您的data.value,此时您所有使用data 的代码都将获得新值。
如何以及何时将 data.value 更新为新的不可变结构可以通过使其从用于更新状态的 setter 函数自动触发来解决。
另一种方法是在每个结构级别使用类似的技巧,例如 - 我使用变量的原始拼写:
var parent = {data: Immutable.Map({name: 'John'}) };
var childrens = {data: Immutable.List([
Immutable.Map({name: 'Alice', parent: parent}),
Immutable.Map({name: 'Bob', parent: parent})
])};
但是您必须记住,您所拥有的值不是不可变结构,而是那些引用不可变结构的附加对象,这些对象引入了额外的间接级别。
一些阅读
我的建议是查看其中一些项目和文章:
我希望这个答案即使没有给出一个简单的解决方案也会有所帮助,因为用不可变结构描述可变世界是一个非常有趣且重要的问题。
其他方法
有关不可变性的不同方法,不可变对象代表不一定恒定的数据,请参阅 Yegor Bugayenko 的 Immutable Objects vs. Common Sense 网络研讨会和他的文章:
Yegor Bugayenko 使用术语“不可变”的含义与它在函数式编程上下文中的通常含义略有不同。他不使用不可变或持久的数据结构和函数式编程,而是提倡使用原始意义上的面向对象编程,即您永远不会真正改变任何对象,但您可以要求它改变一些状态,或者改变一些数据,它本身被认为是与对象分离的。很容易想象一个与关系数据库对话的不可变对象。对象本身可以是不可变的,但它仍然可以更新存储在数据库中的数据。很难想象存储在 RAM 中的某些数据可以被认为与数据库中的数据一样独立于对象,但实际上并没有太大区别。如果您将对象视为公开某些行为的自治实体,并且尊重它们的抽象边界,那么将对象视为不同于数据的东西实际上是很有意义的,然后您可以拥有具有可变数据的不可变对象。
如果有任何需要澄清的地方,请发表评论。