【发布时间】:2012-06-22 15:06:59
【问题描述】:
在寻找其他东西时,完全出于巧合,我偶然发现了一些关于案例类继承是多么可怕的问题。有一个叫做ProductN 的东西,坏蛋和国王,精灵和巫师,以及案例类继承如何丢失了某种非常理想的属性。那么案例类继承有什么问题呢?
【问题讨论】:
标签: scala inheritance case-class
在寻找其他东西时,完全出于巧合,我偶然发现了一些关于案例类继承是多么可怕的问题。有一个叫做ProductN 的东西,坏蛋和国王,精灵和巫师,以及案例类继承如何丢失了某种非常理想的属性。那么案例类继承有什么问题呢?
【问题讨论】:
标签: scala inheritance case-class
一个字:平等
case 类提供了equals 和hashCode 的实现。等价关系,称为equals 是这样工作的(即必须具有以下属性):
x; x equals x 是 true(反身)x、y、z;如果x equals y 和y equals z 然后x equals z(传递)x,y;如果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 did not work as expected 和interaction with the pattern matcher。
【讨论】:
ColoredPoint 是-a Point 但反之亦然。可能不得不称它为equals 以外的其他名称……也许subEquals?
canReplace、supersedes、specifies 或 overrides 的反向关系?任何表明它的>=-ness(或>:,如果你喜欢)。我用>= 来命名它似乎比<= 更容易。
case class 只有在没有父级覆盖它的情况下才会得到equals,所以在这种情况下,ColoredPoint 将使用Point 的equals/hashCode(我没有知道这在 2012 年是否已经是这种情况),它是对称的(并且是反射和传递的)。您可能会争辩 ColoredPoint(0, 0, RED) == ColoredPoint(0, 0, GREEN) 不直观,我同意,但问题不在于案例类继承:如果 Point 是覆盖 equals 的非 case 类,您将遇到完全相同的问题。 copy 更是个问题。
这并不完全正确。这比谎言更糟糕。
正如aepurniet 所提到的,在任何情况下,限制定义区域的类后继必须重新定义相等性,因为模式匹配必须完全等同于相等(如果尝试将Point 匹配为ColoredPoint,那么它将不匹配,因为@ 987654324@不存在)。
这有助于理解如何实现案例类层次结构的相等性。
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) = ???
【讨论】: