案例类继承的“错”是什么问题?


68

在寻找其他东西时,我完全出于偶然,偶然发现了几句有关恶性案例类继承的评论。有一个叫做的东西ProductN,可怜的人,国王,精灵和巫师,以及案例类继承如何丢失了某种非常理想的属性。那么,案例类继承有什么问题呢?

Answers:


112

一句话:平等

case类随附提供了equals和的实现hashCode。等价关系,称为这样的equals工作(即必须具有以下属性):

  1. 对于所有人x; x equals xtrue(反身的)
  2. 对于xyz,如果x equals yy equals z然后x equals z(传递)
  3. xy; 如果x equals y那么y equals x(对称)

只要在继承层次结构中允许相等,就可以破坏2和3。下面的示例简单地说明了这一点:

case class Point(x: Int, y: Int)
case class ColoredPoint(x: Int, y: Int, c: Color) extends Point(x, y) 

然后我们有:

Point(0, 0) equals ColoredPoint(0, 0, RED)

但是不是

ColoredPoint(0, 0, RED) equals Point(0, 0)

您可能会争辩说所有类层次结构都可能存在此问题,这是事实。但是,案例类专门用于从开发人员的角度简化平等性(还有其他原因),因此让它们具有非直觉的行为将是自己目标的定义!


还有其他原因。值得注意的是事实copy并非预期的以及与模式匹配器的相互作用


还有一点细节:)?
Ashkan Kh。Nazary 2012年

2
在OO范式中,这种不对称等价似乎是一件有用的事情,就像在类型级别a ColoredPointis-a Point(反之亦然)一样。也许不得不称呼它吧equals……也许subEquals
路易吉·普林格

@LuigiPlinge也许canReplacesupersedesspecifies,或overrides为反向关系?任何表明其>=-ness(或>:您是否喜欢)的内容。对我来说,用>=而不是来命名更容易<=
Dan Burton 2012年

2
泛型的equals很容易实现,可以满足相等性,使类成为比较的成员。复制的东西看起来像是一个bug,应该与模式匹配器进行交互,就像对非case类的层次结构一样。
aepurniet

1
case class只有获得equals当没有了父母的覆盖,所以在这种情况下ColoredPoint会使用Pointequals/ hashCode(我不知道这是否是已经如此,在2012年),这是对称的(和反射和传递)。您可能会争辩说ColoredPoint(0, 0, RED) == ColoredPoint(0, 0, GREEN)是不直观的,我会同意,但是问题不在于案例类继承:如果Point是非case类重写,您会遇到完全相同的问题equalscopy更大的问题。
阿列克谢·罗曼诺夫

-2

总体而言,这不是真的。这比说谎还糟。

正如aepurniet所提到的,在任何情况下,限制定义区域的类后继者都必须重新定义相等性,因为模式匹配必须完全像相等性一样工作(如果尝试匹配PointColoredPoint那么它将不匹配,因为color不存在)。

这样可以理解如何实现案例类层次结构的相等性。

case class Point(x: Int, y: Int)
case class ColoredPoint(x: Int, y: Int, c: Color) extends Point(x, y)

Point(0, 0) equals ColoredPoint(0, 0, RED)  // false
Point(0, 0) equals ColoredPoint(0, 0, null) // true

ColoredPoint(0, 0, RED) equals Point(0, 0)  // false
ColoredPoint(0, 0, null) equals Point(0, 0) // true

最终,即使对于案例类后继者,也可以满足平等关系的要求(不覆盖平等)。

case class ColoredPoint(x: Int, y: Int, c: String)
class RedPoint(x: Int, y: Int) extends ColoredPoint(x, y, "red")
class GreenPoint(x: Int, y: Int) extends ColoredPoint(x, y, "green")

val colored = ColoredPoint(0, 0, "red")
val red1 = new RedPoint(0, 0)
val red2 = new RedPoint(0, 0)
val green = new GreenPoint(0, 0)

red1 equals colored // true
red2 equals colored // true
red1 equals red2 // true

colored equals green // false
red1 equals green // false
red2 equals green // false

def foo(p: GreenPoint) = ???
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.