- 如果我将超类作为普通类而不是case类 - 我将失去所有case类的好处,例如toString,equals,hashCode方法等。
- 如果我保持它作为一个case类,我将违反不从case类继承的规则。
- 如果我在子类中使用组合 - 我将不得不编写大量的方法并将它们重定向到另一个类 - 这意味着很多工作,而且感觉不像Scala风格。
这是一个经常出现的问题,我的建议是创建一个 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章中有解释。
替代继承,使用委托模式如何?
如果你的继承层次结构中两个类有许多共享字段,那么采用委托模式可以减少样板代码量。像这样:
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(_)) => ...
}
好处:
当您声明 PayloadX 和 PayloadY 时,您不必重复所有字段在Something中。
当您创建 Something(... Payload(..))
的实例时,可以重用创建 Something
的代码,无论您是创建 Something(... PayloadX(..))
还是 ...PayloadY
。
缺点:
也许在您的情况下 PayloadX
和 Y
实际上是Something
的真实子类,我的意思是,在您的情况下,委托可能在语义上是错误的?
你将不得不写 something.payload.whatever
而不是简单的 something.whatever
(我想这可能根据特定情况好坏参半?)
我也探讨了这个问题,据我所知,最好的方法是:
让每个案例类都扩展一个共同的特质,该特质定义了每个情况下必须实现的抽象属性
它并没有消除样板代码(完全不消除),但定义了您的情况类必须遵守的合同,同时不会失去情况类的功能集...