Scala案例类的inheritance
我有一个基于Squeryl的应用程序。 我把我的模型定义为案例类,主要是因为我觉得复制方法很方便。
我有两个严格相关的模型。 这些字段是相同的,许多操作是相同的,它们将被存储在同一个数据库表中。 但是有一些行为只有在两种情况中才有意义,或者在两种情况下都是有道理的,但却是不同的。
到目前为止,我只使用了一个单一的案例类,用一个标志来区分模型的types,所有基于模型types的方法都以if开始。 这是烦人的,不太安全。
我想要做的就是将祖先事例类中的常见行为和领域分解,并从中inheritance两个实际的模型。 但是,据我所知,从案例类inheritance是在斯卡拉皱眉,甚至被禁止,如果子类本身是一个案例类(不是我的情况)。
从案例类inheritance我应该注意哪些问题和陷阱? 这样做对我来说是否合理?
避免代码重复的情况下,避免case类inheritance的首选方法是显而易见的:创build一个通用的(抽象)基类:
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
既然这对很多人来说都是一个有趣的话题,那么让我在这里阐述一下。
你可以采取以下的方法:
// 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。
或者,您可以使用组合而不是inheritance:
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
这真的很有用。 现在你不会忘记处理其他types的Person
(人)。 这实质上就是Scala中的Option
类。
如果这对你无关紧要,那么你可以把它封闭起来,把案例分类放到他们自己的文件中。 也许还有作文。
案例类对于价值对象来说是完美的,即不改变任何属性的对象,并且可以与等价对比。
但是在inheritance的情况下实现平等是相当复杂的。 考虑两个类:
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,蓝色)也应该等于Point(1,4),对不对? 但是当然,ColorPoint(1,4,红色)不应该等于ColorPoint(1,4,蓝色),因为它们具有不同的颜色。 你走了,平等关系的一个基本属性被打破了。
更新
你可以使用inheritance特性解决大量的问题,如另一个答案中所述。 更灵活的select通常是使用types类。 看看Scala中的types类有用吗? 或http://www.youtube.com/watch?v=sVMES4RZF-8
在这些情况下,我倾向于使用组合,而不是inheritance
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") }
显然你可以使用更复杂的层次和匹配,但希望这给你一个想法。 关键是要利用案例类提供的嵌套提取器