Scala案例类继承


88

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

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

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

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

从案例类继承时应该注意哪些问题和陷阱?就我而言,这样做有意义吗?


1
您不能从非大小写类继承还是扩展公共特征?
爱德华多

我不确定。这些字段在祖先中定义。我想基于这些字段获取复制方法,相等性等。如果我将父级声明为抽象类,将子级声明为案例类,它将考虑在父级上定义的参数吗?
安德里亚(Andrea)2012年

我认为不是,您必须在抽象父类(或特征)和目标案例类中定义道具。最后,很多样板,但至少输入安全
virtualeyes

Answers:


118

在避免代码重复的情况下避免案例类继承的首选方法是显而易见的:创建一个公共(抽象)基类:

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

83
您所说的“无代码重复”在哪里?是的,案例类与其父类之间已定义了合同,但您仍在输入道具X2
virtualeyes 2012年

5
@virtualeyes是的,您仍然必须重复这些属性。但是,您不必重复方法,通常方法比属性多。
Malte Schwerhoff,2012年

1
是的,只是希望解决属性的重复问题-另一个答案暗示类型类是一种可能的解决方法;不确定如何,但是似乎更适合混合行为,例如特质,但更灵活。只是样板再造:案例类可以使用,否则,将令人难以置信,它确实可以破解大量的属性定义
virtualeyes 2012年

1
@virtualeyes我完全同意,如果可以轻松地避免重复属性,那就太好了。编译器插件肯定可以解决问题,但我不会称之为简单方法。
Malte Schwerhoff 2012年

13
@virtualeyes我认为避免代码重复不仅是减少编写代码。对我而言,更多的是在应用程序的不同部分中没有相同的代码,而不必在它们之间建立任何联系。使用此解决方案,所有子类都与合同相关联,因此,如果父类发生更改,IDE将能够帮助您确定需要修复的代码部分。
丹尼尔(Daniel)

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

是的,您必须重复这些字段。如果您不这样做,那么就不可能在其他问题之间实现正确的平等。

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

如果重复几个属性对您来说非常重要,请使用常规类,但请记住,它们不太适合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(人)。本质上,这就是OptionScala中的类所做的。

如果这对您来说无关紧要,则可以使其不密封,并将案例类放入其自己的文件中。也许与组成。


1
我认为def name特质必须是val name。我的编译器向我发出了前者无法访问的代码警告。
2015年

13

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

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

class Point(x : Int, y : Int)

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

因此,根据定义,ColorPoint(1,4,red)应该等于Point(1,4),毕竟它们是同一个Point。所以ColorPoint(1,4,blue)也应该等于Point(1,4),对吗?但是,当然,ColorPoint(1,4,red)不应等于ColorPoint(1,4,blue),因为它们具有不同的颜色。到那里,相等关系的一个基本属性被破坏。

更新

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


我理解并同意这一点。因此,当您有一个涉及雇主和雇员的应用程序时,您应该做什么建议。假设它们共享所有字段(名称,地址等),唯一的区别在于某些方法-例如,一个方法可能要定义,Employer.fire(e: Emplooyee)而另一方法则不希望。我想创建两个不同的类,因为它们实际上表示不同的对象,但是我也不喜欢出现的重复。
安德里亚(Andrea)

有一个类型类方法的示例,这里有问题?即关于案例类
virtualeyes

@virtualeyes可以为各种实体具有完全独立的类型,并提供类型类以提供行为。这些类型类可以尽可能多地使用继承,因为它们不受案例类语义契约的约束。在这个问题上有用吗?不知道,这个问题还不够具体。
詹斯·肖特

@JensSchauder看来,特征在行为方面提供了相同的东西,只是不如类型类灵活。我正在讨论案例类属性的非重复性,特征或抽象类通常可以帮助避免这种情况。
virtualeyes 2012年

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")
}

显然,您可以使用更复杂的层次结构和匹配项,但希望可以为您提供一个思路。关键是要利用案例类提供的嵌套提取器


3
这似乎是这里唯一没有重复字段的唯一答案
Alan Thomas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.