当类型聚合其他类型时如何避免样板代码

How to avoid boilerplate code when type aggregates other types

我想为我的域建模。我从聚合开始:

type Tree() =
  member x.Plant = ...
  member x.Trim = ...
  member x.Uproot = ...

type FruitTree(tree : Tree) =
  member x.Trim = ... // redefine
  member x.PickFruit = ...
  // and also make available members from Tree type without following boilerplate code to forward calls
  member x.Plant = tree.Plant
  member x.Uproot = tree.Uproot

如何使聚合对象中的成员可用而无需拼写出来?

编辑

继承时,继承类型的成员在新创建类型的public表面。有没有办法在不暴露实际字段的情况下使用聚合类型的容器获得类似的效果?

有几种方法可以做到这一点,这实际上取决于您实际在做什么 - 树木和果树的例子永远不会足够现实。查看 Scott Wlaschin's Domain Driven Design 资料,其中对选项进行了很好的总结。

在 F# 中,人们通常避免继承而使用组合。您可以编写一个如下所示的包装器:

type FruitTree(tree : Tree) =
  member x.Tree = tree  // Expose the wrapped tree
  member x.PickFruit =  // Add other functionality here

这在某些情况下可能有效。如果您正在寻找功能更强大的解决方案,则应该将数据与操作分开。然后你可以定义看起来像这样的数据结构:

type Tree = 
  | Ordinary of int     // Some data about tree - say, number of branches
  | Fruit of Tree * int // Annotates another tree with number of fruits

然后就可以写函数对树进行操作了:

let rec trim tree =
  match tree with
  | Ordinary(n) -> Ordinary(n-1) // Remove one branch from the tree
  | Fruit(t, f) -> Fruit(trim t, f) // Recursively apply on the wrapped tree

也就是说,您确实需要举一个更具体的例子才能得到有用的答案..

这题问的是继承的核心特征。它可能是一个很好的解决方案,只使用它。只要

继承就没有根本问题
  • 结果类型的特征是基本类型特征的完美超集,并且
  • 它不会使结果代码难以推理。

通常不鼓励继承,因为人们在上述几点之一不成立的地方使用它。这会导致混乱的类型公开他们不应该公开的成员;然后,这些成员的名字不当、无关紧要甚至无效。这会导致大型、复杂、不直观的类型,通常最好禁止从整个项目继承。

组合也有助于集群的复杂性;如果一个类型很复杂,明确地将它分解成更小的部分可以使推理更容易。

但是草率的程序员滥用它们并不是继承特性的错。在基础 class 不复杂且派生 class 应按原样公开所有基础 class 功能的情况下,继承将使代码简洁、一致且更易于维护。

也就是说,目前在继承和组合之间没有 "middle way" 成员的显式转发。如果继承恰好表示给定的关系,则使用它;如果没有,任何转发都必须手动完成。在任何一种情况下,请考虑对不应泄漏到库的 API.

的成员使用 internal 访问修饰符