如何在需要层次结构时使用案例类?

19
我知道你不能继承case类,但如果确实需要怎么做?我们有两个层次结构的类,都包含许多字段,并且我们需要能够创建这两个类的实例。以下是我的选择:
  • 如果我将超类作为普通类而不是case类 - 我将失去所有case类的好处,例如toString,equals,hashCode方法等。
  • 如果我保持它作为一个case类,我将违反不从case类继承的规则。
  • 如果我在子类中使用组合 - 我将不得不编写大量的方法并将它们重定向到另一个类 - 这意味着很多工作,而且感觉不像Scala风格。
我该怎么办?这不是一个很常见的问题吗?
3个回答

15

这是一个经常出现的问题,我的建议是创建一个 trait 包含所有父级属性,创建一个只实现该 trait 的 case class,然后再创建一个继承该 case class 并包含更多属性的子类。

sealed trait Parent {
  /* implement all common properties */
}

case class A extends Parent

case class B extends Parent {
  /*add all stuff you want*/
}

一个很好的理解方式是将其视为一棵树,特质(trait)是节点,而样例类(case class)是叶子。

您可以根据需要使用trait或抽象类作为父类。但是,请避免使用class,因为您将能够创建它的实例,这并不优雅。

编辑:如评论中所建议的那样,您可以封闭(seal)trait以便在模式匹配时如果没有覆盖所有case class就在编译时出现异常。例如,在“Scala编程”第15.5章中有解释。


1
树的概念很棒,有助于理解。 - linello
抽象类在这里更合适。 - Sergey Kostrukov

6

替代继承,使用委托模式如何?

如果你的继承层次结构中两个类有许多共享字段,那么采用委托模式可以减少样板代码量。像这样:

case class Something(aa: A, bb: B, cc: C, payload: Payload)

sealed abstract class Payload

case class PayloadX(xx: X) extends Payload
case class PayloadY(yy: Y) extends Payload

然后你可以像这样创建Something实例:

val sth1 = Something('aa', 'bb', 'cc', PayloadX('xx'))
val sth2 = Something('aa', 'bb', 'cc', PayloadY('yy'))

而且你可以进行模式匹配:

sth1 match {
  case Something(_, _, _, PayloadX(_)) => ...
  case Something(_, _, _, PayloadY(_)) => ...
}

好处:

  • 当您声明 PayloadXPayloadY 时,您不必重复所有字段在Something中。

  • 当您创建 Something(... Payload(..)) 的实例时,可以重用创建 Something 的代码,无论您是创建 Something(... PayloadX(..)) 还是 ...PayloadY

缺点:

  • 也许在您的情况下 PayloadXY 实际上是Something的真实子类,我的意思是,在您的情况下,委托可能在语义上是错误的?

  • 你将不得不写 something.payload.whatever 而不是简单的 something.whatever(我想这可能根据特定情况好坏参半?)


1
另一个缺点是:您将无法在方法签名中讨论特定类型,它将始终是“Something”。 - Victor Basso

4

我也探讨了这个问题,据我所知,最好的方法是:

让每个案例类都扩展一个共同的特质,该特质定义了每个情况下必须实现的抽象属性

它并没有消除样板代码(完全不消除),但定义了您的情况类必须遵守的合同,同时不会失去情况类的功能集...


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