不变性和共享参考 - 如何协调?

dav*_*000 10 scala immutability

考虑这个简化的应用领域:

  • 刑事调查数据库
  • Person 是参与调查的任何人
  • Report 是调查的一部分信息
  • A Report引用主要Person(调查的主题)
  • A Report具有次要相关的共犯(并且在其他调查或报告中肯定是主要的
  • 这些类具有用于将它们存储在数据库中的ID,因为它们的信息可能随时间而变化(例如,我们可能会找到一个人的新别名,或者将一个感兴趣的人添加到报告中)

域名http://yuml.me/13fc6da0

如果这些存储在某种数据库中并且我希望使用不可变对象,则似乎存在关于状态和引用的问题.

假设我改变了一些关于a的元数据Person.由于我的Person对象是不可变的,我可能会有一些代码:

class Person(
    val id:UUID,
    val aliases:List[String],
    val reports:List[Report]) {

  def addAlias(name:String) = new Person(id,name :: aliases,reports)
}
Run Code Online (Sandbox Code Playgroud)

因此,我Person的新别名成为一个新对象,也是不可变的.如果a Report指的是那个人,但别名在系统的其他地方被改变了,我Report现在指的是"老"人,即没有新别名的人.

同样,我可能会:

class Report(val id:UUID, val content:String) {
  /** Adding more info to our report */
  def updateContent(newContent:String) = new Report(id,newContent)
}
Run Code Online (Sandbox Code Playgroud)

由于这些对象不知道是谁引用它们,因此我不清楚如何让所有"引用者"知道有一个新对象可用来代表最近的状态.

这可以通过让所有对象从中央数据存储"刷新"并且创建新的,更新的对象的所有操作存储到中央数据存储来完成,但这感觉就像是对基础语言的引用的简洁重新实现.也就是说,让这些"次要可存储物体"变得可变更为明确.因此,如果我向a添加别名Person,则所有引用者都会看到新值而不执行任何操作.

当我们想要避免可变性时,如何解决这个问题,或者这是不可变性无效的情况?

Rex*_*err 10

如果X指向Y,两者都是不可变的,并且Y更改(即用更新的副本替换它),那么你别无选择,只能替换X(因为它已经改变,因为新的X指向新的Y,不是旧的).

在高度互联的数据结构中维护这种情况很快就会成为头疼 你有三种一般方法.

  • 一般忘记不变性.使链接可变.根据需要修复它们.确保你确实修复了它们,否则你可能会遇到内存泄漏(X指的是旧的Y,它指的是旧的X,它指的是较旧的Y等).
  • 不要存储直接链接,而是存储您可以查找的ID代码(例如,键入哈希映射).然后,您需要处理查找失败案例,但其他方面非常强大.当然,这比直接链接慢一点.
  • 改变整个世界.如果某些内容发生了变化,那么链接到它的所有内容也必须进行更改(并且在复杂的数据集中同时执行此操作很棘手,但理论上可行,或者至少可以隐藏它的可变方面,例如有很多惰性值) .

我希望,哪个更好取决于您的查找和更新速度.


Ale*_*xey 5

我建议你阅读他们如何处理clojure和Akka中的问题.阅读有关软件事务内存的信息.还有一些我的想法......

不变性不是为了它本身而存在的.不变性是抽象的.它本质上不"存在".世界是可变的,世界永远在变化.因此,数据结构可变是很自然的 - 它们描述了在给定时刻的真实或模拟对象的状态.它看起来像OOP rulez.在概念层面,这种态度的问题是RAM中的对象!=真实对象 - 数据可能不准确,它带有延迟等

因此,在最琐碎的要求的情况下,你可以使用一切可变的 - 人,报告等实际问题将出现在:

  1. 数据结构是从并发线程修改的
  2. 用户为相同的对象提供令人震惊的更改
  3. 用户提供无效数据,应该回滚

使用天真的可变模型,您很快就会得到不一致的数据和破碎系统.可变性容易出错,不可变性是不可能的.你需要的是世界的交易视图.在交易程序中看到不可变的世界.STM管理以一致且线程安全的方式应用的更改.