F# 在F中实现构建器模式(la System.Text.StringBuilder)#

F# 在F中实现构建器模式(la System.Text.StringBuilder)#,f#,F#,突变状态位于构建器模式的中心。是否有一种惯用的方法在F#中实现这样一个类的内部结构,从而减少/消除可变状态,同时保留通常的接口(这个类将主要从其他.NET语言中使用) 下面是一个简单的实现: type QueryBuilder<'T>() = //' let where = ref None let orderBy = ref None let groupBy = ref None member

突变状态位于构建器模式的中心。是否有一种惯用的方法在F#中实现这样一个类的内部结构,从而减少/消除可变状态,同时保留通常的接口(这个类将主要从其他.NET语言中使用)

下面是一个简单的实现:

type QueryBuilder<'T>() =                              //'
    let where = ref None
    let orderBy = ref None
    let groupBy = ref None
    member x.Where(cond) =
        match !where with
        | None -> where := Some(cond)
        | _ -> invalidOp "Multiple WHERE clauses are not permitted"
    // members OrderBy and GroupBy implemented similarly
类型QueryBuilder,其中:=Some(cond)
|->invalidOp“不允许使用多个WHERE子句”
//成员OrderBy和GroupBy的实现方式类似
一个想法是创建一个记录类型来存储内部内容,并使用复制和更新表达式

type private QueryBuilderSpec<'T> =                     //'
    { Where : ('T -> bool) option;                      //'
      OrderBy : (('T -> obj) * bool) list;              //'
      GroupBy : ('T -> obj) list }                      //'

type QueryBuilder<'T>() =                               //'
    let spec = ref None
    member x.Where(cond) =
        match !spec with
        | None -> 
            spec := Some({ Where = Some(cond); OrderBy = []; GroupBy = [] })
        | Some({ Where = None; OrderBy = _; GroupBy = _} as s) -> 
            spec := Some({ s with Where = Some(cond) })
        | _ -> invalidOp "Multiple WHERE clauses are not permitted"
    // members OrderBy and GroupBy implemented similarly
type private QueryBuilderSpec
spec:=Some({Where=Some(cond);OrderBy=[];GroupBy=[]})
|一些({Where=None;OrderBy=\ GroupBy=\作为s)->
spec:=Some({s,其中=Some(cond)})
|->invalidOp“不允许使用多个WHERE子句”
//成员OrderBy和GroupBy的实现方式类似
这一切似乎有点笨拙,当试图在F#中实现命令式模式时,这可能是意料之中的。有没有更好的方法来做到这一点,同样,为了命令式语言而保留常用的构建器界面?

从内部消除可变性似乎对我没有多大意义。。。通过设计,你使它变了——在这一点上,任何技巧都不会真正改变任何东西

至于简洁性-
让mutable
尽可能好(这样您就不需要使用
来取消引用):


type QueryBuilder一种替代方法是只使用F#记录类型,默认值为无/空:

type QueryBuilderSpec<'T> =
    { Where : ('T -> bool) option;
      OrderBy : (('T -> obj) * bool) list;
      GroupBy : ('T -> obj) list }

let Default = { Where = None; OrderBy = None; GroupBy = [] }
如果愿意,您可以使用“with”进一步复制“myVal”,从而“构建”更多属性,同时保持原始属性不变:

let myVal' = { myVal with GroupBy = [fun x -> x.Whatever] }

我认为,根据您的用例,您最好使用不可变的实现。下面的例子 将静态强制任何生成器在生成之前只设置一次其where、order和group属性, 尽管它们可以按任何顺序设置:

type QueryBuilder<'t,'w,'o,'g> = 
  internal { where : 'w; order : 'o; group : 'g } with

let emptyBuilder = { where = (); order = (); group = () }

let addGroup (g:'t -> obj) (q:QueryBuilder<'t,_,_,unit>) : QueryBuilder<'t,_,_,_> =
  { where = q.where; order = q.order; group = g }

let addOrder (o:'t -> obj * bool) (q:QueryBuilder<'t,_,unit,_>) : QueryBuilder<'t,_,_,_> =
  { where = q.where; order = o; group = q.group }

let addWhere (w:'t -> bool) (q:QueryBuilder<'t,unit,_,_>) : QueryBuilder<'t,_,_,_> =
  { where = w; order = q.order; group = q.group }

let build (q:QueryBuilder<'t,'t->bool,'t->obj,'t->obj*bool>) =
  // build query from builder here, knowing that all components have been set
而这个不会,因为它从不设定顺序:

let query =
  emptyBuilder
  |> addGroup ...
  |> addWhere ...
  |> build

正如我所说的,这对于您的应用程序来说可能有些过分,但这仅仅是因为我们使用的是不可变的构建器。

我真的看不出构建器之间有多大区别。这两种方法都是可以从外部改变的,无论是第一种方法还是第二种方法都不重要。没错。我看不到任何消除可变状态的方法。区别在于第一个实现可以有任意数量的可变变量。第二个实现有一个。是的,也许差别不大。我讨厌这两个,但这是我需要建立的。我更喜欢F,但也许我需要回到C来做这件事。我只是想在放弃F#之前我会得到更多的意见。使用记录以及复制和更新表达式,或者其他一些函数技术与可变变量(除了减少可变变量的数量)相比,有什么好处吗?如果这主要是从F#使用的话,我会这么做。不幸的是,它的外观/行为必须像典型的.NET类型。虽然,这是我在内部维护状态时使用的方法。在这种情况下,您是否可以向记录类型添加方法以创建它的新副本?语法为“…with x.AddWhere func=…//创建副本”。对于其他.NET语言来说,这些方法看起来就像普通的.NET方法,而记录的行为类似于字符串类。如果我理解你的话,这就是我在OP中的第二个实现中所做的。我非常喜欢kvb使生成器类不可变的想法。然后,每个方法都可以返回一个以更新状态传递的新实例。是的,这与Kvb答案相同,只是客户端代码会将addGroup等视为成员,这在C#看来可能更自然。谢谢你让我觉得自己很愚蠢。:)它应该返回一个新的不可变QueryBuilder,而不是每个成员返回“this”。当然非常感谢。这正是我想要的。顺便说一句-我不完全理解您的代码,但我认为我可以放弃记录类型,使类类型不可变(从每个“构建器”方法返回一个新实例),这将非常有效。@Daniel-请参阅我的更新以了解我试图显示的内容的进一步解释。你完全正确,这对非记录不可变类型同样有效。现在我明白了。这太棒了,非常适合我要做的事情。你给我的比我要求的还要多。谢谢
type QueryBuilder<'t,'w,'o,'g> = 
  internal { where : 'w; order : 'o; group : 'g } with

let emptyBuilder = { where = (); order = (); group = () }

let addGroup (g:'t -> obj) (q:QueryBuilder<'t,_,_,unit>) : QueryBuilder<'t,_,_,_> =
  { where = q.where; order = q.order; group = g }

let addOrder (o:'t -> obj * bool) (q:QueryBuilder<'t,_,unit,_>) : QueryBuilder<'t,_,_,_> =
  { where = q.where; order = o; group = q.group }

let addWhere (w:'t -> bool) (q:QueryBuilder<'t,unit,_,_>) : QueryBuilder<'t,_,_,_> =
  { where = w; order = q.order; group = q.group }

let build (q:QueryBuilder<'t,'t->bool,'t->obj,'t->obj*bool>) =
  // build query from builder here, knowing that all components have been set
let query = 
  emtpyBuilder
  |> addGroup ...
  |> addOrder ...
  |> addWhere ...
  |> build
let query =
  emptyBuilder
  |> addGroup ...
  |> addWhere ...
  |> build