使用循环引用设计对不可变类的批判,以及更好的选择 [英] Critique of immutable classes with circular references design, and better options

查看:155
本文介绍了使用循环引用设计对不可变类的批判,以及更好的选择的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个工厂类创建带有循环引用的对象。我想他们是不可变的(在某种意义上的话)。因此,我使用以下技术,使用关闭排序:

I have a factory class that creates objects with circular references. I'd like them to be immutable (in some sense of the word) too. So I use the following technique, using a closure of sorts:

[<AbstractClass>]
type Parent() =
  abstract Children : seq<Child>
and Child(parent) =
  member __.Parent = parent

module Factory =

  let makeParent() =
    let children = ResizeArray()
    let parent = 
      { new Parent() with
        member __.Children = Seq.readonly children }
    [Child(parent); Child(parent); Child(parent)] |> children.AddRange
    parent

AddChild 方法,因为有更强的保证不变性。也许是神经质,但我喜欢闭包访问控制。

I like this better than an internal AddChild method because there's a stronger guarantee of immutability. Perhaps it's neurotic, but I prefer closures for access control.

这个设计有什么缺陷吗?有没有更好,也许较不繁琐的方法来做到这一点?

Are there any pitfalls to this design? Are there better, perhaps less cumbersome, ways to do this?

推荐答案

您可以使用F#支持递归初始化,即使在创建抽象类的实例时:

You can use F#'s support for recursive initialization even when creating an instance of abstract class:

let makeParent() =
  let rec children = seq [ Child(parent); Child(parent); Child(parent) ]
  and parent = 
    { new Parent() with
      member __.Children = children }
  parent

编译代码时,F#使用惰性值,因此值 children 变为惰性值,属性 Children 访问此延迟计算的值。这很好,因为它可以首先创建 Parent (引用惰性值)的实例,然后实际构造序列。

When compiling the code, F# uses lazy values, so the value children becomes a lazy value and the property Children accesses the value of this lazy computation. This is fine, because it can first create instance of Parent (referencing the lazy value) and then actually construct the sequence.

对记录做同样的事情不会很好,因为没有一个计算会被延迟,但是在这里工作相当不错,因为在创建 Parent (如果它是一个记录,这将是一个字段,必须进行评估)。

Doing the same thing with records wouldn't work as nicely, because none of the computations would be delayed, but it works quite nicely here, because the sequence is not actually accessed when creating the Parent (if it was a record, this would be a field that would have to be evaluated).

F#编译器不能是否正确,因此会发出警告,可使用 #nowarn40禁用。

The F# compiler cannot tell (in general) whether this is correct, so it emits a warning that can be disabled using #nowarn "40".

一般,我认为使用让rec ..和.. 初始化递归值是一件好事 - 它是一个有限的(其中一个引用必须被延迟)但它强制你保持递归引用隔离,我认为它保持你的代码更简单。

In general, I think that using let rec .. and .. to initialize recursive values is a good thing - it is a bit limited (one of the references must be delayed), but it forces you to keep the recursive references isolated and, I think, it keeps your code simpler.

编辑可能会出错 - 如果 Child 的构造函数尝试访问其父级的 Children 集合,在它之前的惰性值可以被创建,并且你得到一个运行时错误(这是警告说的)。尝试将此添加到 Child 的构造函数:

EDIT To add an example when this may go wrong - if the constructor of Child tries to access the Children collection of its parent, then it forces evaluation of the lazy value before it can be created and you get a runtime error (which is what the warning says). Try adding this to the constructor of Child:

do printfn "%d" (Seq.length parent.Children)

这篇关于使用循环引用设计对不可变类的批判,以及更好的选择的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆