【问题标题】:How do I manipulate a tree of immutable objects?如何操作不可变对象树?
【发布时间】:2010-04-06 17:22:02
【问题描述】:

我正在用不可变对象构建一个完整的应用程序,这样多线程和撤消变得更容易实现。我正在使用Google Collections Library,它提供了 Map、List 和 Set 的不可变版本。

我的应用程序模型看起来像一棵树:

  • Scene 是一个顶级对象,其中包含对根节点的引用。
  • 每个节点都可以包含子节点和端口。

对象图可能如下所示:

Scene
 |
 +-- Node
      |
      +-- Node 
           |
           +- Port
      +-- Node
           |
           +- Port
           +- Port

如果所有这些对象都是不可变的,由顶级 SceneController 对象控制:

  • 构建此层次结构的最佳方法是什么?
  • 如何替换对象树中任意深度的对象?
  • 有没有办法支持反向链接,例如具有“父”属性的节点?

更一般地说:

  • 是否出现了处理此类数据的任何模式?
  • 是否有关于该主题的(学术)文献?
  • 这是个好主意吗?

【问题讨论】:

  • 你已经尝试过什么?为什么列出的 3 个问题中的每一个问题的简单解决方案对您来说都不够好?
  • 我有自己的构造/替换实现,但我很想听听其他人遇到的模式。
  • 您可能会发现获取 Chris Okasaki 的 Purely Functional Data Structures 副本很有用。
  • 我已经多次看过 Stack Overflow 上提到的 Okasaki 书。好吗?有没有类似的 Java 书籍?
  • 有一个 PDF 可用,但它并没有涵盖本书的所有内容。看看它。而且,据我所知,目前还没有关于为非函数式语言编写的函数式数据结构和技术的书籍,这很遗憾。

标签: java class-design immutability


【解决方案1】:

这里有两个有趣的概念。首先,持久数据结构。如果树的所有元素都是不可变的,那么可以通过替换一些部分但引用旧部分从原始树派生一棵新树,从而节省时间和内存。

例如,如果您要向已经有两个端口的节点添加第三个端口,则必须创建一个新场景、一个新场景的节点的后代以及您正在更改的节点。不需要重新创建另一个节点和所有端口——您只需在新的场景/节点中引用它们即可。

另一个概念是拉链。拉链是一种通过持久数据结构“导航”以优化本地更改的方法。例如,如果您添加了四个新端口而不是一个,但您一次添加了一个端口,您必须创建四个新场景和八个新节点。使用拉链,您可以将此类创作推迟到完成后再进行,从而节省了这些中间对象。

我读过的关于拉链的最好解释是here

现在,使用 zipper 导航数据结构不再需要反向链接。通过巧妙地使用递归构造函数,您可以在不可变结构中拥有反向链接。但是,这样的数据结构不会持久。非持久不可变数据结构的修改性能很差,因为您每次都需要复制整个数据。

至于学术文献,我推荐 Okasaki 的 Purely Function Data Structures (dissertation PDF, fully fledged book)。

【讨论】:

  • +1 提到了 Zippers 和 Okasaki,从字面上看,他们写了关于这个主题的书。另一个有趣的概念是 Clojure 1.1 的 transient 数据结构。 (基本上,一种暂时的非持久性数据结构。)事实上,Clojure 总的来说很有趣:如果 Okasaki 写了关于函数式数据结构的书,Rich Hickey 写了这个库。而且,顺便说一句:Clojure 数据结构专门以这样一种方式编写,它们可以用作 Java 库。它们完全独立于 Clojure 语言和 Clojure 标准库。
  • 拉链链接已损坏,这是一个有效的scienceblogs.com/goodmath/2010/01/13/…
  • @Sergio 谢谢,已修复。
【解决方案2】:

如果你的树是不可变的,那么如果你想以任何方式改变它,你就必须生成一棵新树。

这听起来很糟糕,但如果您的所有节点也是不可变的,那就不是了!由于您不需要复制不可变对象,因此您的新树将主要引用旧树,但您所做的更改除外。

您必须以这样一种方式设计您的树,即每棵不可变树都引用其他不可变树。这样您也不需要复制整个不可变树。

但是如果你走不可变的树路径,那么你就不能有反向链接。否则不能重用子树。

【讨论】:

  • 我发现该模型非常类似于 Git 版本控制系统,其中更改文件会导致文件以及树和所有上述树发生更改。对于反向链接,是否没有可以为特定版本的树解析的“别名”方法或“路径说明符”?
  • 反向链接是什么意思?因为如果一个节点链接到父节点并且父节点发生更改,您将不得不重新生成所有子节点和孙子节点等。对于一次更改来说,这是很多工作。
  • 好吧,getParent() 方法将是节点父节点的反向链接。如果节点有父属性,我将无法重用原始节点。我想知道是否有更聪明的方法可以做到这一点,例如 Unix 的“符号链接”。
  • 我不确定您是否可以在没有更高级别的参考结构或构建自己的伪参考模型的情况下做到这一点
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-23
  • 2013-08-30
  • 2014-02-20
  • 1970-01-01
  • 1970-01-01
  • 2014-12-07
  • 2020-03-22
相关资源
最近更新 更多