为啥“避免方法重载”?

Posted

技术标签:

【中文标题】为啥“避免方法重载”?【英文标题】:Why "avoid method overloading"?为什么“避免方法重载”? 【发布时间】:2011-01-31 09:25:59 【问题描述】:

为什么Jorge Ortiz advise to avoid method overloading?

【问题讨论】:

这和Scala继承了Java的类型擦除有什么关系吗? @Justin:类型擦除与此有什么关系? 你为什么不问问 Jorge Ortiz 为什么他不建议方法重载? 不确定它是否适用,因为我不知道 Jorge 的初衷,但是:michid.wordpress.com/2008/02/08/… 呸... bit.ly/aduyIn :'( 【参考方案1】:

重载使得将方法提升为函数有点困难:

object A 
   def foo(a: Int) = 0
   def foo(b: Boolean) = 0
   def foo(a: Int, b: Int) = 0

   val function = foo _ // fails, must use = foo(_, _) or (a: Int) => foo(a)

您不能选择性地导入一组重载方法中的一个。

在尝试应用隐式视图以使参数适应参数类型时,更有可能出现歧义:

scala> implicit def S2B(s: String) = !s.isEmpty                             
S2B: (s: String)Boolean

scala> implicit def S2I(s: String) = s.length                               
S2I: (s: String)Int

scala> object test  def foo(a: Int) = 0; def foo(b: Boolean) = 1; foo("") 
<console>:15: error: ambiguous reference to overloaded definition,
both method foo in object test of type (b: Boolean)Int
and  method foo in object test of type (a: Int)Int
match argument types (java.lang.String)
       object test  def foo(a: Int) = 0; def foo(b: Boolean) = 1; foo("") 

它可以悄悄地使默认参数不可用:

object test  
    def foo(a: Int) = 0; 
    def foo(a: Int, b: Int = 0) = 1 

就个人而言,这些原因并不能迫使您完全避免超载。我觉得我错过了一些更大的问题。

更新

证据堆积如山。

它使spec 复杂化 它可以render implicits unsuitable 用于视图边界。 它限制了您只能在其中一个重载选项中引入默认参数。 因为参数的类型将没有预期的类型,所以您不能传递像 '_.foo' as arguments to overloaded methods 这样的匿名函数字面量。

更新 2

您不能(当前)在包对象中使用重载方法。 API 调用者的适用性错误为harder to diagnose。

更新 3

静态重载解析会剥夺所有类型安全性的 API:
scala> object O  def apply[T](ts: T*) = (); def apply(f: (String => Int)) = () 
defined object O

scala> O((i: String) => f(i)) // oops, I meant to call the second overload but someone changed the return type of `f` when I wasn't looking...

【讨论】:

目前,在 scalac 中还有一个 bug,在某些情况下由重载触发。 issues.scala-lang.org/browse/SI-7596. 前两个问题不会影响重载的每一次有效使用。第三期提交a bug report。 restriction on defaults is by choice,理论上可以修复。 _.foo 问题的错误是 Scala 的有限类型推断,而不是重载。您回答了这个问题,但其中一些原因是由于 Scala 中可以改进的其他弱点。重载比运行时向下转换析取更有效,或者名称的笛卡尔积是嘈杂的并且与共享语义断开连接。 类型类当前为unable to be used generally due to lack of a first-class union type。是的,我在 Scala 的社区中看到了这种态度,但想象一下 addIntToDoubleaddDoubleToInt,即名称的笛卡尔积,而不是每个常见语义的静态类型。用命名代替打字似乎是倒退的。 Java got more things correct 可能比我们认识的要多。 I wrote 在讨论线程中(对于我在之前评论中提到的错误报告),“什么是邪恶的 IMO 期望重载不是它,或者降低了拥有一个的重要性通用语义的名称”。 答案和 cmets 对我来说似乎很幼稚,并且没有提到使用重载的一个最重要的原因:当一个方法实际上必须根据传递给它的类型执行非常不同的内部逻辑时。 “使用隐式”的答案立即失败,因为可能不存在从一个对象到体现专门逻辑的不同类型的任何可能转换。【参考方案2】:

Gilad 和 Jason(retronym)给出的理由都是尽可能避免重载的很好理由。 Gilad 的原因集中在为什么重载通常会出现问题,而 Jason 的原因则集中在为什么它在其他 Scala 特性的上下文中会出现问题。

在 Jason 的列表中,我要补充一点,重载与类型推断的交互作用很差。考虑:

val x = ...
foo(x)

x 推断类型的更改可能会改变调用哪个 foo 方法。 x 不需要改变,只是 x 的推断类型,这可能由于各种原因而发生。

出于所有给出的原因(还有一些我确定我忘记了),我认为方法重载应该尽可能少用。

【讨论】:

如果您不希望这种情况发生,您可以声明 x 的类型。如果你不宣布它,那么你就是在说你希望它改变。对于具有相同数量参数的每个重载,foo 的语义应该相同,否则设计不正确。至于限制奇异级联推理更改的范围,公共方法应始终声明其返回类型。我认为这是影响版本之间 Scala 二进制兼容性的问题之一。【参考方案3】:

我认为该建议并不是特别针对 scala,而是针对一般的 OO(到目前为止,我知道 scala 应该是 OO 和功能之间的最佳选择)。

覆盖很好,它是多态性的核心,也是面向对象设计的核心。

另一方面,

重载问题更大。使用方法重载很难辨别哪个方法会真正被调用,而且它确实经常引起混淆。也很少有理由说明为什么真的需要重载。这个问题大部分时间都可以通过其他方式解决,我同意超载是一种气味。

这里是an article,它很好地解释了我所说的“重载是混乱的根源”,我认为这是不鼓励它的主要原因。它适用于 java,但我认为它也适用于 scala。

【讨论】:

无论如何,Scala 并不是主要的面向对象语言。 @ewenli - Jorge 在 scala 社区中广为人知,Rahul 提供的链接是 Jorge 的 scala 技巧之一,但您的回答无法说明为什么重载不好 特别是 scala ,这显然是问题的意图。另外,我不知道您为什么认为该问题以任何方式感到困惑-您应该将其从答案中删除,因为它完全没有道理。 -1 @Daniel Scala 主要是一种面向对象的语言。你有什么理由不这么认为? 哦,竞争性函数式语言的创建者认为 Scala 的功能性还不够!大震撼! :) @Daniel Scala 可能是多范式,但它仍然主要是一种面向对象的语言。 Scala 的功能特性被实现为 面向对象的特性。即使是功能最强大的 Scala 程序也将仅由对象及其各自的类和特征组成。现在,Martin Odersky 可以对他的语言说任何他想说的话(毕竟这是 他的 语言),但是,在严格的技术评估中,Scala 主要是面向对象的,这里的“主要”是指其他一切都建立在这个特性之上。

以上是关于为啥“避免方法重载”?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 ruby​​ 不支持方法重载?

为啥这行得通?方法重载+方法覆盖+多态

为啥 WCF 中不允许方法重载?

为啥没有为不同的返回类型定义方法重载?

为啥java中需要方法重载和覆盖? [复制]

为啥我的方法“DropDownListFor”没有重载?