2012-10-03 105 views
69

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

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

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

我想要做的是在祖先案例类中考虑常见行为和领域,并让它们继承两个实际模型。但是,据我所知,从Scala继承的案例类继承了,甚至被禁止,如果子类本身是一个案例类(不是我的情况)。

What are the problems and pitfalls I should be aware in inheriting from a case class? Does it make sense in my case to do so?

+1

难道你不能从非案例类继承,还是扩展一个共同的特质? – Eduardo

+0

我不确定。这些字段在祖先中定义。我想根据这些字段获得复制方法,平等等。如果我将父项声明为抽象类,将子项声明为案例类,它是否会考虑在父项中定义的帐户参数? – Andrea

+0

我想不是,你必须在抽象父母(或特质)和目标案例类中定义道具。最后,很多人的样板,但至少键入安全 – virtualeyes

回答

89

我的首选避免情况下的类继承不重复的代码的方式是有些显而易见的:如果你想更细粒度的

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 
+48

你说的这个“没有代码重复”在哪里?是的,案例类与其父母之间定义了合同,但您仍然在输入道具X2 – virtualeyes

+2

@virtualeyes正确的是,您仍然需要重复属性。但是,您不必重复使用方法,这通常相当于比属性更多的代码。 –

+1

是的,只是希望能够解决属性的重复问题 - 另一个答案暗示类型类是一种可能的解决方法;然而,不知道怎么样更像面向混合行为,如性状,但更灵活。只是样板重:case类,可以忍受,就好像它是相当否则不可思议,真的可以砍出的属性定义伟大的大片 – virtualeyes

12

案例类适用于值对象,即不会更改任何属性并可与equals进行比较的对象。

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

class Point(x : Int, y : Int) 

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

所以根据定义重点色(1,4,红色)应该等于该点(1,4),它们是相同的点毕竟。所以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

+0

我理解并同意这一点。那么,当你有一个处理雇主和雇员的应用程序时,你应该怎么做?假设它们共享所有字段(名称,地址等),唯一的区别在于某些方法 - 例如,可能需要定义“Employer.fire(e:Emplooyee)”,但不能相反。我想制作两个不同的课程,因为它们实际上代表了不同的对象,但我也不喜欢所出现的重复。 – Andrea

+0

得到了一个这里的问题类型类方法的例子?即关于案例类别 – virtualeyes

+0

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

36

由于这对很多人来说都是一个有趣的话题,请让我在这里点亮一些。

你可以用下面的方法去:

// 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类。

如果这对你并不重要,那么你可以使它不被密封,并将案例类放入他们自己的文件中。也许还有写作。

+1

我认为trait中的'def name'需要是'val name'。我的编译器给了我前者无法访问的代码警告。 – BAR

3

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

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

显然,你可以使用更复杂的层次和比赛,但希望这给你的想法。关键是要利用案例类提供的嵌套提取器