Подтвердить что ты не робот

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

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

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

Что действительно полезно. Теперь вы не забудете иметь дело с другими типами Person (люди). Это по существу то, что делает класс Option в Scala.

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

Ответ 3

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

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

class Point(x : Int, y : Int)

и

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

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

Обновление

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

Ответ 4

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

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

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