【问题标题】:How to do Parent-Child Relationship in F#如何在 F# 中处理亲子关系
【发布时间】:2017-09-29 20:39:43
【问题描述】:

我正在尝试将我很久以前编写的 C# 应用程序重写为 F#,但我遇到了一些麻烦,因为这是我第一次尝试在 F# 中编写有意义的东西

在我的 C# 软件中,我有以下类“Buildings”,它是我需要在应用程序的其余部分中使用的父类:

public class Buildings()
{
    Buildings()
    {
        Spectre = new Spectres(this);
    }
    public double Height;
}

还有另一个名为“Spectres”的类,它只在建筑物中使用:

public class Spectres(Buildings prntBuilding)
{
    Spectres()
    {
        _prntBuilding = prntBuilding;
    }

    private Buildings _prntBuilding;
    public double doSomethingToheight()
    {
       return _prntBuilding.Height * 2.0;
    }
}

我的问题是:我如何在 F# 中做到这一点,在 F# 中这种父子关系是否有替代方案?

编辑:我找到了解决方案

type Buildings() as self =
   member this.Hn = 1.0 with get, set
   member val Spectre = new Spectres(self) with get, set

and Spectres(prntBuinding : Buildings) = 
   let DoSomethingToHeight = prntBuilding.Hn * 2.0

【问题讨论】:

  • 在 F# 中,数据通常只是普通数据,您可以在这里使用记录类型。如果您尝试编写惯用的 F#,请考虑不存储对父级的引用。然后,您可能会在接下来需要对数据做一些事情的地方遇到困难,但这将是另一个问题的问题,并且可能会有解决它的模式。

标签: c# .net oop f#


【解决方案1】:

您的问题可能是由 F# 中的声明顺序影响第二种类型的可见性引起的。 (F# 中的顺序很重要。)

你可以用相互递归的类型来解决这个问题,比如:

type Buildings() =

    member val Height = 0.0 with get, set

and Spectres(prntBuilding : Buildings) =

    member __.DoSomething () = prntBuilding.Height * 2.0

and 表示他们彼此认识。

相互递归类型的文档在这里:https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/classes#mutually-recursive-types

【讨论】:

    【解决方案2】:

    最好的方法是重新考虑你的设计。

    为什么Spectres 类需要知道它的父类?在您向我们展示的示例代码中,Spectres 类只需要知道其父类即可为doSomethingToheight 方法获取一个输入参数(高度)。那么,为什么不直接将高度传递给该方法呢?

    let doSomethingToHeight height = height * 2.0
    

    作为一个额外的好处,这个版本的doSomethingToHeight 函数是一个函数:它没有副作用,它的结果完全由它的输入参数决定。纯函数有很多优点:它们更容易测试,也更容易在代码的其他部分重用。您会惊讶于它的价值:一旦您开始将方法重写为纯函数,您最终会发现可以在其他地方重用它们的各种方法。

    顺便说一句,现在您的示例代码中的 Spectres 类在 C# 中看起来像这样(我将保留 C# 中的示例以表明这种设计方法不仅限于 F#,而且因为您更熟悉此时阅读 C# 代码):

    public class Spectres()
    {
        Spectres(Buildings prntBuilding)
        {
            _prntBuilding = prntBuilding;
        }
    
        private Buildings _prntBuilding;
        public double doSomethingToheight(double height)
        {
           return height * 2.0;
        }
    }
    

    现在Spectres 没有特别的理由需要保留对父建筑的引用。 (至少在这个示例代码中;在您的原始代码中,您可能还有其他用途。但我正在使用您迄今为止向我们展示的内容)。所以让我们摆脱那些不必要的构造函数参数和私有字段:

    public class Spectres()
    {
        Spectres()
        {
        }
    
        public double doSomethingToheight(double height)
        {
           return height * 2.0;
        }
    }
    

    现在,这是一个只有一个函数的类,除了支持这个函数之外没有其他工作。你读过史蒂夫·耶格的精彩文章"Execution in the Kingdom of the Nouns"吗?只携带一个函数的类应该替换为那个函数。在 C# 中,这有点棘手,因为所有函数都必须存在于某个类中,但是您可以通过在某个实用程序类中将其设置为静态方法来获得最接近独立函数的方法,在该实用程序类中保留 all 的独立函数功能:

    public static class StandaloneFunctions
    {
        public static double doSomethingToHeight(double height)
        {
            return height * 2.0;
        }
    }
    

    当然,在 F# 中,这七行可以简化为我在此答案开头向您展示的简单的一行定义:

    let doSomethingToHeight height = height * 2.0
    

    不需要为该函数声明一个静态类; F# 编译器将在幕后为您处理这些问题。您所要做的就是考虑您的数据,以及您的数据将如何转换。

    此时,您可能会说:“是的,但我的 Building 班级与 Spectre 有其他您不知道的交互,并且由于这些交互,您的解决方案将无法工作。”好吧,如果您对这些交互使用类似的方法(将它们转换为纯函数),我敢打赌您会发现它确实有效,而且从长远来看更容易理解。如果您不确定如何进行转换,请针对您不确定的部分提出另一个问题;周围有几位 F# 专家可能会很乐意为您提供帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多