转述问题:
当一个对象在不可变集合中发生变化时,是否可以自动更新所有相关对象?
简答
不。
长答案
不,但在不可变的数据结构中没有任何变化,所以这不是问题。
更长的答案
比较复杂...
不变性
不可变对象的全部意义在于,如果您有对不可变对象的引用,您就不必费心检查它的任何属性是否已更改。那么,这不是问题吗?好...
结果
这会产生一些后果 - 它们是好是坏取决于您的期望:
- 之间不存在差异通过噪声值和通过噪声参考语义
- 一些比较可以更容易
- 当你在某处传递一个对象的引用时,你不必担心代码的其他部分会改变它
- 当你从某个地方得到一个对象的引用时,你知道它永远不会改变
- 你避免了一些并发问题,因为没有时间变化的概念
- 当没有任何变化时,您不必担心更改是否是原子性的
- 使用不可变数据结构更容易实现软件事务内存(STM)
但世界是可变的
当然,在实践中我们经常处理随时间变化的值。看起来不可变状态不能描述可变世界,但人们有一些处理它的方法。
以这种方式看待它:如果您在某个 ID 上有您的地址并且您搬到了另一个地址,那么该 ID 应该更改为与新的真实数据一致,因为您住在该地址不再是真实的。但是,当您在购买包含地址的东西时收到发票,然后更改地址时,发票将保持不变,因为在编写发票时您仍然住在该地址。现实世界中的某些数据表示是不可变的,例如该示例中的发票,而有些则是可变的,例如 ID。
现在以您的示例为例,如果您选择使用不可变结构来建模您的数据,您必须以一种您从我的示例中考虑发票的方式来考虑它。数据可能不是最新的,但它在某个时间点始终是一致和真实的,并且永远不会改变。
如何应对变化
那么如何使用不可变数据对变化进行建模呢?在Clojure 中使用Vars、Refs、Atoms和Agents解决了一个很好的方法,并且ClojureScript(一个面向 JavaScript 的 Clojure 编译器)支持其中的一些(特别是 Atoms 应该像在 Clojure 中一样工作,但有没有 Refs、STM、Vars 或 Agents - 看看ClojureScript 和 Clojure 在并发特性方面有什么区别)。
看看Atoms 在 ClojureScript 中是如何实现的,您似乎可以使用普通的 JavaScript 对象来实现相同的功能。它适用于诸如拥有本身可变的 JavaScript 对象之类的事情,但它有一个属性是对不可变对象的引用——您将无法更改该不可变对象的任何属性,但您将能够构造一个不同的不可变对象,并将旧的对象交换到顶级可变对象中的新对象。
其他像Haskell这样纯函数式的语言可能有不同的方式来处理可变世界,比如monad(一个众所周知的难以解释的概念——Douglas Crockford,JavaScript: The Good Parts 的作者和JSON 的发现者将其归因于“monadic 诅咒”在他的谈话中单子和性腺)。
你的问题看似简单,其实涉及的问题很复杂。当然,如果对您的问题回答“否”是否可以在一个对象发生变化时自动更新所有相关对象,那当然会错过这一点,但它比这更复杂,并说在不可变对象中没有任何变化(所以这个问题永远不会发生)同样无济于事。
可能的解决方案
您可以拥有一个始终访问所有结构的顶级对象或变量。假设你有:
var data = { value: Immutable.Map({...}) }
如果您总是使用data.value
(或使用一些更好的名称)访问您的数据,那么您可以将 传递data
给代码的其他部分,并且每当您的状态发生变化时,您只需为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 和他的文章的不可变对象与常识网络研讨会:
Yegor Bugayenko 使用术语“不可变”的含义与函数式编程上下文中通常的含义略有不同。他不使用不可变或持久的数据结构和函数式编程,而是提倡使用面向对象编程的原始意义,即您实际上从未真正改变任何对象,但您可以要求它改变某些状态,或改变某些数据,它本身被认为是与对象分开的。很容易想象一个与关系数据库对话的不可变对象。对象本身可以是不可变的,但它仍然可以更新存储在数据库中的数据。很难想象存储在 RAM 中的某些数据可以被认为与数据库中的数据一样与对象分开,但实际上并没有太大区别。
如果有什么需要澄清的,请发表评论。