提问者:小点点

Scala 案例类继承


我有一个基于Squeryl的应用程序。我将模型定义为case类,主要是因为我发现使用复制方法很方便。

我有两个严格相关的模型。这些字段是相同的,许多操作是共同的,它们将存储在同一个DB表中。但有些行为只在这两种情况中的一种情况下有意义,或者在两种情况下都有意义,但不同。

到目前为止,我只使用了一个case类,用一个标志来区分模型的类型,所有基于模型类型的不同方法都以if开头。这很烦人,也不太安全。

我想做的是考虑祖先case类中的常见行为和字段,并让两个实际模型从中继承。但是,据我所知,从case类继承在Scala中是不受欢迎的,如果子类本身是case类(不是我的case),甚至是被禁止的。

在继承一个案例类时,我应该注意哪些问题和陷阱?在我的情况下这样做有意义吗?


共3个答案

匿名用户

我在避免代码重复的情况下避免案例类继承的首选方法有些明显:创建一个通用(抽象)基类:

abstract class Person {
  def name: String
  def age: Int
  // address and other properties
  // methods (ideally only accessors since it is a case class)
}

case class Employer(val name: String, val age: Int, val taxno: Int)
    extends Person

case class Employee(val name: String, val age: Int, val salary: Int)
    extends Person


如果要更细粒度,请将属性分组为单个特征:

trait Identifiable { def name: String }
trait Locatable { def address: String }
// trait Ages { def age: Int }

case class Employer(val name: String, val address: String, val taxno: Int)
    extends Identifiable
    with    Locatable

case class Employee(val name: String, val address: String, val salary: Int)
    extends Identifiable
    with    Locatable

匿名用户

由于这对许多人来说是一个有趣的话题,让我在这里阐明一下。

您可以采用以下方法:

// You can mark it as 'sealed'. Explained later.
sealed trait Person {
  def name: String
}

case class Employee(
  override val name: String,
  salary: Int
) extends Person

case class Tourist(
  override val name: String,
  bored: Boolean
) extends Person

是的,你必须复制字段。如果不这样做,就不可能在其他问题中实现正确的平等。

但是,您不需要复制方法/函数。

如果几个属性的重复对你来说很重要,那么使用常规类,但是记住它们不太适合FP。

或者,您可以使用组合来代替继承:

case class Employee(
  person: Person,
  salary: Int
)

// In code:
val employee = ...
println(employee.person.name)

构图是一种有效且合理的策略,您也应该考虑。

如果您想知道密封特征的含义 - 它只能在同一个文件中扩展。也就是说,上面的两个事例类必须位于同一个文件中。这允许进行详尽的编译器检查:

val x = Employee(name = "Jack", salary = 50000)

x match {
  case Employee(name) => println(s"I'm $name!")
}

给出一个错误:

warning: match is not exhaustive!
missing combination            Tourist

这真的很有用。现在您不会忘记处理其他类型的Persons(人)。这本质上是Scala中的Option类所做的。

如果这对您来说无关紧要,那么您可以将其设置为非密封,并将case类放入它们自己的文件中。也许还有作曲。

匿名用户

case类非常适合值对象,即不改变任何属性并且可以与equals进行比较的对象。

但是在存在继承的情况下实现平等是相当复杂的。考虑两个类:

class Point(x : Int, y : Int)

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

所以根据定义,科罗拉多点(1,4,红色)应该等于点(1,4),它们毕竟是同一个点。所以科罗拉多点(1,4,蓝色)也应该等于点(1,4),对吗?但是当然,科罗拉多点(1,4,红色)不应该等于科罗拉多点(1,4,蓝色),因为它们有不同的颜色。好了,相等关系的一个基本属性被打破了。

使现代化

你可以像在另一个答案中描述的那样,利用性状的遗传来解决许多问题。一个更灵活的选择是使用类型类。看看Scala中的类型类有什么用?或者http://www.youtube.com/watch?v=sVMES4RZF-8