Scala案例类继承

And*_*rea 84 inheritance scala case-class

我有一个基于Squeryl的应用程序.我将我的模型定义为案例类,主要是因为我觉得有方便的复制方法.

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

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

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

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

Mal*_*off 107

我避免使用没有代码重复的case类继承的首选方法有点明显:创建一个公共(抽象)基类:

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
Run Code Online (Sandbox Code Playgroud)


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

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
Run Code Online (Sandbox Code Playgroud)

  • 你说的"没有代码重复"在哪里?是的,合同是在案例类和其父类之间定义的,但您仍然在键入道具X2 (76认同)
  • @virtualeyes我认为避免代码重复不仅仅是为了少写.对我来说,更多的是在应用程序的不同部分没有相同的代码段而它们之间没有任何关联.使用此解决方案,所有子类都与合同绑定,因此如果父类更改,IDE将能够帮助您识别需要修复的代码部分. (11认同)
  • @virtualeyes是的,你还是要重复这些属性.但是,您不必重复方法,这通常比属性更多代码. (4认同)

Kai*_*ren 40

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

你可以采用以下方法:

// 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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

// In code:
val employee = ...
println(employee.person.name)
Run Code Online (Sandbox Code Playgroud)

组合是一种有效且合理的策略,您也应该考虑.

如果你想知道密封特性是什么意思 - 它只能在同一个文件中扩展.也就是说,上面的两个案例类必须在同一个文件中.这允许详尽的编译器检查:

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

x match {
  case Employee(name) => println(s"I'm $name!")
}
Run Code Online (Sandbox Code Playgroud)

给出错误:

warning: match is not exhaustive!
missing combination            Tourist
Run Code Online (Sandbox Code Playgroud)

这真的很有用.现在你不会忘记处理其他类型的Person人(人).这基本上就是OptionScala中的类所做的.

如果这对您无关紧要,那么您可以将其设置为非密封并将案例类放入自己的文件中.或许可以选择合成.


Jen*_*der 13

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

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

class Point(x : Int, y : Int)
Run Code Online (Sandbox Code Playgroud)

class ColoredPoint( x : Int, y : Int, c : Color) extends Point
Run Code Online (Sandbox Code Playgroud)

因此根据定义,ColorPoint(1,4,红色)应该等于Point(1,4),毕竟它们是相同的Point.所以ColorPoint(1,4,蓝色)也应该等于Point(1,4),对吧?但是ColorPoint(1,4,红色)当然不应该等于ColorPoint(1,4,蓝色),因为它们有不同的颜色.你去了,平等关系的一个基本属性被打破了.

更新

您可以使用特征中的继承来解决许多问题,如另一个答案中所述.更灵活的替代方案通常是使用类型类.请参阅Scala中的哪些类型类有用?http://www.youtube.com/watch?v=sVMES4RZF-8


小智 7

在这些情况下,我倾向于使用组合而不是继承,即

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")
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这似乎是这里唯一没有重复字段的唯一答案 (2认同)