Scala наследование класса case
У меня есть приложение, основанное на Squeryl. Я определяю свои модели как классы case, в основном, так как удобнее иметь методы копирования.
У меня две модели, которые строго связаны друг с другом. Поля одинаковы, многие операции являются общими, и они должны храниться в одной таблице DB. Но существует некоторое поведение, которое имеет смысл только в одном из двух случаев или имеет смысл в обоих случаях, но отличается.
До сих пор я использовал только один класс case с флагом, который отличает тип модели, и все методы, которые отличаются в зависимости от типа модели, начинаются с if. Это раздражает и не совсем безопасно.
То, что я хотел бы сделать, - это фактор общего поведения и полей в классе класса предков и унаследовать от них две фактические модели. Но, насколько я понимаю, наследование классов case недовольно в Scala и даже запрещено, если подкласс сам является классом case (не мой случай).
Каковы проблемы и проблемы, которые я должен знать о наследовании из класса case? Имеет ли смысл в моем случае сделать это?
Ответы
Ответ 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")
}
Очевидно, вы можете использовать более сложную иерархию и совпадения, но, надеюсь, это дает вам представление. Ключ состоит в том, чтобы использовать вложенные экстракторы, которые предоставляют классы классов