我有一个基于Squeryl的应用程序。我将我的模型定义为案例类,主要是因为我觉得复制方法很方便。
我有两个严格相关的模型。字段相同,许多操作是共同的,它们将存储在同一个DB表中。 但是有一些行为只在两种情况中的一种情况下有意义,或者在两种情况下都有意义但是不同。
到目前为止,我只使用了一个案例类,其中一个标志区分了模型的类型,所有基于模型类型的方法都以if开头。这很烦人,不太安全。
我想要做的是考虑祖先案例类中的常见行为和字段,并让两个实际模型继承它。但是,据我所知,继承自case类在Scala中是不受欢迎的,如果子类本身就是一个case类(不是我的情况),它甚至被禁止。
从案例类继承我应该注意哪些问题和陷阱?在我的情况下这样做是否有意义?
答案 0 :(得分:103)
我避免使用没有代码重复的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
如果您想要更细粒度,请将属性分组为单个特征:
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
答案 1 :(得分:39)
由于对许多人来说这是一个有趣的话题,让我在这里阐明一些。
您可以采用以下方法:
// 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
是的,您必须复制这些字段。如果不这样做,则无法实现正确的等式among other problems。
但是,您不需要复制方法/功能。
如果重复一些属性对您来说非常重要,那么请使用常规类,但请记住它们不适合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
哪个真的很有用。现在你不会忘记处理其他类型的Person
(人)。这基本上就是Scala中的Option
类所做的。
如果这对您无关紧要,那么您可以将其设置为非密封并将案例类放入自己的文件中。或许可以选择合成。
答案 2 :(得分:13)
案例类非常适合于价值对象,即不会改变任何属性并可与equals进行比较的对象。
但是在继承存在的情况下实现equals相当复杂。考虑两个类:
class Point(x : Int, y : Int)
和
class ColoredPoint( x : Int, y : Int, c : Color) extends Point
因此根据定义,ColorPoint(1,4,红色)应该等于Point(1,4),毕竟它们是相同的Point。所以ColorPoint(1,4,蓝色)也应该等于Point(1,4),对吧?但是ColorPoint(1,4,红色)当然不应该等于ColorPoint(1,4,蓝色),因为它们有不同的颜色。你去了,平等关系的一个基本属性被打破了。
<强>更新强>
您可以使用特征中的继承来解决许多问题,如另一个答案中所述。更灵活的替代方案通常是使用类型类。请参阅What are type classes in Scala useful for?或http://www.youtube.com/watch?v=sVMES4RZF-8
答案 3 :(得分:6)
在这些情况下,我倾向于使用构图而不是继承,即
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")
}
显然,你可以使用更复杂的层次结构和匹配,但希望这会给你一个想法。关键是要利用案例类提供的嵌套提取器