我有一个工厂类,用于创建带有循环引用的对象。我希望它们也是不变的(在某种意义上说)。所以我使用以下技术,使用一种类型的闭包:
[<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
我比internal
AddChild
方法更喜欢这个,因为它有更强的不变性保证。也许它是神经质的,但我更喜欢关闭访问控制。
这个设计有什么陷阱吗?有没有更好的,也许不那么繁琐的方法来做到这一点?
答案 0 :(得分:9)
即使在创建抽象类的实例时,也可以使用F#的支持进行递归初始化:
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
的实例(引用惰性值)然后实际构造序列。
对记录做同样的事情不会很好,因为没有计算会被延迟,但是这里工作得很好,因为在创建Parent
时实际上并没有访问序列(如果它是一个记录,这将是一个必须评估的领域。)
F#编译器无法判断(通常)这是否正确,因此它会发出警告,可以使用#nowarn "40"
禁用。
一般来说,我认为使用let rec .. and ..
来初始化递归值是一件好事 - 它有点受限(其中一个引用必须被延迟),但它会强制你保持递归引用被隔离并且我认为,它可以使您的代码更简单。
编辑要在可能出错的情况下添加示例 - 如果Child
的构造函数尝试访问其父级的Children
集合,则会强制评估在创建它之前的延迟值并且您得到运行时错误(这是警告所说的)。尝试将其添加到Child
:
do printfn "%d" (Seq.length parent.Children)
答案 1 :(得分:4)
我认为托马斯的答案是要走的路。但是,为了完整性,我将展示如何使用递归记录来创建循环不可变对象。这可能会非常难看,所以我将不可变记录实现隐藏在一些更好的属性背后:
type Parent = internal { children : Children option }
and internal Children = { first : Child; rest : Children option }
and Child = internal { parent : Parent }
let rec internal listToChildren = function
| [] -> None
| c::cs -> Some { first = c; rest = listToChildren cs }
let rec internal childrenToList = function
| None -> []
| Some { first = c; rest = cs } -> c::(childrenToList cs)
module Factory =
let makeParent() =
let rec parent = { children = children }
and child1 = { parent = parent }
and child2 = { parent = parent }
and child3 = { parent = parent }
and children = [child1; child2; child3] |> listToChildren
parent
type Parent with
member p.Children = childrenToList p.children
type Child with
member c.Parent = c.parent
答案 2 :(得分:0)
我想这样的事情也可以做到:
type ParentFactory private (n) as X =
inherit Parent()
let childs = [for i=1 to n do yield Child(X :> Parent)]
override X.Children = childs |> List.toSeq;
static member Create n = (new ParentFactory(n)) :> Parent