Scala案例类继承

Scala案例类继承,scala,inheritance,case-class,Scala,Inheritance,Case Class,我有一个基于Squeryl的应用程序。我将模型定义为case类,主要是因为我发现使用copy方法很方便 我有两个模型是严格相关的。字段是相同的,许多操作是通用的,它们将存储在同一个DB表中但有些行为只在两种情况中的一种情况下有意义,或者在两种情况下都有意义,但不同 到目前为止,我只使用了一个case类,带有区分模型类型的标志,所有基于模型类型的不同方法都以if开头。这很烦人,而且不太安全 我想做的是考虑祖先案例类中的常见行为和字段,并从中继承两个实际模型。但是,据我所知,Scala不赞成从cas

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

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

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

我想做的是考虑祖先案例类中的常见行为和字段,并从中继承两个实际模型。但是,据我所知,Scala不赞成从case类继承,甚至在子类本身是case类(不是我的case)的情况下也禁止继承

在继承case类时,我应该注意哪些问题和陷阱?对我来说这样做有意义吗


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

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

class Point(x : Int, y : Int)

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

更新


正如另一个答案中所描述的,你可以用遗传特性来解决很多问题。一个更灵活的选择通常是使用类型类。请参见或

避免案例类继承而不复制代码的首选方法有些明显:创建公共(抽象)基类:

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

由于这对许多人来说是一个有趣的话题,让我在这里说明一些情况

您可以采用以下方法:

//您可以将其标记为“密封”。稍后解释。
密封特征人{
定义名称:字符串
}
案例类员工(
覆盖val名称:字符串,
薪金:整数
)延伸人
案例级游客(
覆盖val名称:字符串,
无聊:布尔值
)延伸人
是的,您必须复制字段。如果不这样做,就不可能实现正确的平等

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

如果一些属性的重复对您来说非常重要,那么就使用常规类,但请记住它们并不适合FP

或者,您可以使用组合而不是继承:

案例类员工(
人:人,,
薪金:整数
)
//代码:
val employee=。。。
println(employee.person.name)
作文是一个有效的、合理的策略,你也应该考虑。

如果你想知道一个密封的特征意味着什么——它是只能在同一个文件中扩展的东西。也就是说,上面的两个case类必须位于同一个文件中。这允许进行详尽的编译器检查:

val x=Employee(name=“Jack”,工资=50000)
x匹配{
案例员工(姓名)=>println(s“我是$name!”)
}
给出一个错误:

warning: match is not exhaustive!
missing combination            Tourist
这真的很有用。现在,您不会忘记处理其他类型的
Person
s(人)。这本质上就是Scala中的
选项
类所做的


如果这对您来说无关紧要,那么您可以将其设置为非密封的,并将case类放入它们自己的文件中。在这些情况下,我倾向于使用合成而不是继承,即

sealed trait IVehicle // tagging trait

case class Vehicle(color: String) extends IVehicle

case class Car(vehicle: Vehicle, doors: Int) extends IVehicle

val vehicle: IVehicle = ...

vehicle match {
  case Car(Vehicle(color), doors) => println(s"$color car with $doors doors")
  case Vehicle(color) => println(s"$color vehicle")
}

显然,您可以使用更复杂的层次结构和匹配,但希望这能给您一个想法。关键是要利用case类提供的嵌套提取器

难道你不能从非case类继承,或者扩展一个公共特性吗?我不确定。字段在祖先中定义。我想得到基于这些字段的复制方法、等式等等。如果我将父类声明为抽象类,子类声明为case类,它会考虑父类上定义的参数吗?我认为不会,您必须在抽象父类(或trait)和目标case类中定义props。最后,罗特的o’样板,但至少我理解并同意类型安全。那么,当你有一个与雇主和雇员打交道的应用程序时,你建议应该做什么呢。假设他们共享所有字段(姓名、地址等),唯一的区别在于某些方法——例如,人们可能希望定义
Employer.fire(e:employee)
,但不是相反。我想创建两个不同的类,因为它们实际上代表不同的对象,但我也不喜欢出现重复。这里有一个类型类方法的例子吗?i、 e.关于案件classes@virtualeyes可以为各种实体提供完全独立的类型,并提供类型类来提供行为。这些类型类可以使用继承,因为它们不受case类的语义约定的约束。在这个问题上有用吗?“我不知道,这个问题还不够具体,”Jenschauder看来特质在行为方面提供了相同的东西,只是不如类型类灵活;我正在讨论case类属性的非重复性,trait或抽象类通常会帮助人们避免这种情况。你所说的“无代码重复”在哪里?是的,合同是有定义的
sealed trait IVehicle // tagging trait

case class Vehicle(color: String) extends IVehicle

case class Car(vehicle: Vehicle, doors: Int) extends IVehicle

val vehicle: IVehicle = ...

vehicle match {
  case Car(Vehicle(color), doors) => println(s"$color car with $doors doors")
  case Vehicle(color) => println(s"$color vehicle")
}