Скала случае наследования классов
У меня есть приложение, основанное на 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 при наличии наследования довольно сложна. Рассмотрим два класса:
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
Comments