gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 22:52:40 27 4
gpt4 key购买 nike

变异状态是构建器模式的核心。是否有一种惯用的方式来实现 F# 中此类的内部,这将减少/消除可变状态,同时保留通常的接口(interface)(此类将主要用于其他 .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

一个想法是创建一个记录类型来存储内部,并使用复制和更新表达式。
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

这一切似乎有点笨拙,也许在尝试在 F# 中实现命令式模式时应该预料到这一点。有没有更好的方法来做到这一点,为了命令式语言,再次保留通常的构建器界面?

最佳答案

我认为根据您的用例,使用不可变实现可能会更好。下面的例子
将静态强制任何构建器在构建之前将其 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

显然,您可能必须针对您的特定约束对其进行调整,并将其公开给其他语言,您可能希望在另一个类和委托(delegate)上使用成员而不是 let-bound 函数和 F# 函数类型,但您明白了。

更新

也许值得扩展我所做的更多描述 - 代码有点密集。使用记录类型没有什么特别之处;一个普通的不可变类也一样好——代码会不那么简洁,但与其他语言的互操作可能会更好。我的实现基本上有两个重要特征
  • 每个添加方法都返回一个表示当前状态的新构建器。这相当简单,尽管它明显不同于通常如何实现 Builder 模式。
  • 通过使用额外的泛型类型参数,您可以强制执行非平凡的不变量,例如在使用 Builder 之前,要求几个不同的属性中的每一个都被指定一次。对于某些应用程序来说,这可能是多余的,而且有点棘手。只有不可变的 Builder 才有可能,因为我们可能需要在操作后返回具有不同类型参数的 Builder。

  • 在上面的示例中,类型系统将允许此操作序列:
    let query = 
    emtpyBuilder
    |> addGroup ...
    |> addOrder ...
    |> addWhere ...
    |> build

    而这个不会,因为它从不设置顺序:
    let query =
    emptyBuilder
    |> addGroup ...
    |> addWhere ...
    |> build

    正如我所说,这对您的应用程序来说可能有点过头了,但这只是因为我们使用了不可变的构建器。

    关于f# - 在 F# 中实现构建器模式(a la System.Text.StringBuilder),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2267119/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com