Наследование класса case Scala

У меня есть приложение, основанное на Squeryl. Я определяю свои модели как классы case, в основном потому, что мне удобно иметь методы копирования.

У меня есть две модели, которые неразрывно связаны. Поля одинаковы, многие операции являются общими, и они должны храниться в одной таблице БД. но существует некоторое поведение, которое имеет смысл только в одном из двух случаев или имеет смысл в обоих случаях, но отличается.

до сих пор я использовал только один класс case с флагом, который отличает тип модели, и все методы, которые отличаются в зависимости от типа модели, начинаются с if. Это раздражает и не совсем безопасно.

то, что я хотел бы сделать, это фактор общего поведения и полей в классе случая предка и наследовать от него две фактические модели. Но, насколько я понимаю, наследование от классов case не одобряется в Scala и даже запрещено, если подкласс сам является классом case (не в моем случае).

какие проблемы и подводные камни я должен знать при наследовании от класса case? Есть ли смысл в моем случае сделать?

4 ответов


мой предпочтительный способ избежать наследования класса 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

поскольку это интересная тема для многих, позвольте мне пролить свет здесь.

вы можете пойти со следующим подходом:

// 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 (люди). Это по существу то, что Option класс в Scala делает.

если это не имеет значения для вас, то вы можете сделать его не запечатанным и бросить классы case в свои собственные файлы. И, возможно, пойти с композицией.


классы case идеально подходят для объектов value, т. е. объектов, которые не изменяют никаких свойств и могут быть сравнены с equals.

но реализация equals при наличии наследования довольно сложна. Рассмотрим два класса:

class Point(x : Int, y : Int)

и

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

Итак, по определению колорпойнт(1,4,красный цвет) должна быть равна точке(1,4) они одинаковые ведь. Таким образом, ColorPoint (1,4, blue) также должен быть равен Point(1,4), правильно? Но, конечно, ColorPoint(1,4,красный) не должен равняться ColorPoint (1,4,синий), потому что они имеют разные цвета. Вот так, одно основное свойство отношения равенства нарушено.

обновление

вы можете использовать наследование от признаков, решающих множество проблем, как описано в другом ответе. Еще более гибкой альтернативой часто является использование классов типов. См.для чего полезны классы типов в Scala? или http://www.youtube.com/watch?v=sVMES4RZF-8


в этих ситуациях я склонен использовать композицию вместо наследования, т. е.

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

очевидно, вы можете использовать более сложную иерархию и совпадения, но, надеюсь, это даст вам идею. Ключ состоит в том, чтобы воспользоваться вложенными экстракторами, которые предоставляют классы case