如何选择一个包含被动过载的大型方法和一堆小型过载的方法之间的区别?每个小方法都只做少量的工作。

7

有两种实现重载的方法。第一种是在一个方法/构造函数中完成所有操作,并从其他重载方法中调用它,这会导致方法体变得更长。第二种方法是在每个重载中只做最少的工作,因此有时代码难以导航和理解哪个重载执行了什么。

例如,如果一个类 Cat 的两个重载如下:

public Cat(string name, int? weight, Color mainColor);
public Cat(string name);

有两种实现方法:

第一种类型

public Cat(string name, int? weight, Color mainColor)
{
    // Initialize everything.
    this.name = name;
    if (weight.HasValue) this.weight = weight.Value;

    // There is a bug here (see the anwer of @Timwi): mainColor can be null.
    this.colors = new List<Colors>(new[] { mainColor });
}

public Cat(string name)
    : this(name, null, null)
{
    // Nothing else to do: everything is done in the overload.
}

第二种类型

public Cat(string name)
{
    // Initialize the minimum.
    this.name = name;
    this.colors = new List<Colors>();
}

public Cat(string name, int? weight, Color mainColor)
    : this(name)
{
    // Do the remaining work, not done in the overload.
    if (weight.HasValue) this.weight = weight.Value;
    this.colors.Add(mainColor);
}

问题

  1. 这两种过载类型分别被称为什么(以便在互联网或书籍中查找更多信息)?
  2. 在选择这些类型时,必须考虑哪些主要因素/问题

注意:由于C# 4.0允许您指定可选参数,为避免歧义,假设我只谈论C# 3.0。


如果您想避免歧义,您将不得不限制使用C# 3,而不是.NET Framework 3.x,因为C# 4可以针对.NET Framework 3.x进行目标设置。 - Roman Starkov
@romkyns:注意已经修订。因此,问题仅涉及.NET Framework 3.0(或FxCop兼容的.NET 4.0解决方案)。 - Arseni Mourzenko
C# 4编译器可以并且确实可以在针对.NET Framework 2.0时编译可选参数。我现在明白了,你只是对可选参数不感兴趣,但为什么不直接说呢?为什么要以一种不会真正阻止你使用可选参数的方式来指定框架版本呢? - Roman Starkov
@romkyns:啊,好的。抱歉。问题已编辑。我总是搞混C#的版本和.NET的版本,所以再次道歉。 - Arseni Mourzenko
2个回答

3
我认为这是另一个例子,其中没有单一的、教条式的答案能够合理地涵盖所有情况。我将始终查看个别情况,并根据所有可用因素进行决策。 其中一个因素是第一个有很多条件语句。你的代码也有一个漏洞:你会将一个空值添加到颜色列表中;为了修复这个漏洞,你需要更多的条件语句。这样的构造函数很容易变得混乱。大量的条件语句表明,在许多情况下,逻辑显着不同,因此为每种情况分别创建构造函数是非常合理的。 然而,在没有那么多条件语句的情况下,逻辑对所有情况都是相同的,因此现在调用一个单一的构造函数来执行这个逻辑,并且做得很好是有意义的。然后只有一个地方需要维护它。 另一个因素是,在你的示例中,第一个构造函数使weight未初始化。这不一定是一件坏事,因为幸运的是,C#中的默认初始化是可预测的;但是如果字段声明为weight初始化为非零值,并且只有某些构造函数将该默认值覆盖为另一个值,则我认为这是一种不好的形式。构造函数参数和/或this(...)调用是更好的文档化该字段默认值的位置。(最好是构造函数参数,因为这样甚至客户端程序员也可以看到默认值,但显然这需要C# 4。)当然,还可以使用字段初始化器初始化所有字段,并使构造函数为空,如果只有一个没有参数的构造函数,则这是一种合理的策略。 所以,就像你说的那样,你不希望方法体变得太长,但你也不希望代码变得太难以导航,因此你需要在任何给定情况下在两者之间取得平衡。

谢谢,我现在更清楚如何在这两种方法之间进行选择了。顺便说一下,在我的情况下,“weight”字段的类型是“int”,而不是“int?”(否则我会直接写“this.weight = weight”)。是的,对于此字段的默认0值是可以预期的。顺便说一句,感谢您指出了将null值添加到列表中存在的错误。 - Arseni Mourzenko

1

第一个

构造函数重载必须始终相互调用或调用公共的初始化方法。这是重构和代码模块化的问题,这样您只需要进行一次更改。

例如,如果您想要添加以下内容:

this.name = name ?? string.Empty;
在第二种情况下,你需要在第二个地方做两次,但在第一个地方只需要做一次。 此外,按照惯例,构造函数的顺序应该从最少参数开始。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,