Dan*_*iel 6 f# closures class-design circular-dependency immutability
我有一个工厂类,用于创建带有循环引用的对象.我希望它们也是不变的(在某种意义上说).所以我使用以下技术,使用一种类型的闭包:
[<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
Run Code Online (Sandbox Code Playgroud)
我比一种internal AddChild方法更喜欢这个,因为它有更强的不变性保证.也许它是神经质的,但我更喜欢关闭访问控制.
这个设计有什么陷阱吗?有没有更好的,也许不那么繁琐的方法来做到这一点?
即使在创建抽象类的实例时,您也可以使用F#的支持进行递归初始化:
let makeParent() =
let rec children = seq [ Child(parent); Child(parent); Child(parent) ]
and parent =
{ new Parent() with
member __.Children = children }
parent
Run Code Online (Sandbox Code Playgroud)
编译代码时,F#使用延迟值,因此该值children变为惰性值,属性Children访问此延迟计算的值.这很好,因为它可以首先创建Parent(引用惰性值)的实例,然后实际构造序列.
使用记录执行相同的操作不会很好,因为没有计算会被延迟,但它在这里工作得很好,因为在创建时实际上没有访问序列Parent(如果它是记录,这将是一个必须评估的字段).
F#编译器无法判断(通常)这是否正确,因此它会发出可以使用禁用的警告#nowarn "40".
一般来说,我认为使用let rec .. and ..初始化递归值是一件好事 - 它有点受限(其中一个引用必须被延迟),但它会强制你保持递归引用被隔离,我认为它保留了你的代码简单.
编辑要添加一个示例,当这可能出错 - 如果构造函数Child尝试访问Children其父集合,那么它会强制评估延迟值,然后才能创建它并且您得到运行时错误(这是警告所说的).尝试将此添加到以下构造函数Child:
do printfn "%d" (Seq.length parent.Children)
Run Code Online (Sandbox Code Playgroud)