У меня есть приложение на базе Squeryl. Я определяю свои модели как классы case, в основном потому, что мне удобно иметь методы копирования.
У меня есть две модели, которые строго связаны. Поля совпадают, многие операции являются общими, и они должны храниться в одной таблице БД. Но есть поведение, которое имеет смысл только в одном из двух случаев или имеет смысл в обоих случаях, но отличается.
До сих пор я использовал только один класс case с флагом, который определяет тип модели, и все методы, которые различаются в зависимости от типа модели, начинаются с if. Это раздражает и не совсем безопасен.
Что я хотел бы сделать, так это разложить на множители общее поведение и поля в классе-предке и унаследовать от него две фактические модели. Но, насколько я понимаю, наследование от классов case не одобряется в Scala и даже запрещено, если подкласс сам является классом case (не в моем случае).
Какие проблемы и подводные камни я должен знать при наследовании от класса case? Имеет ли смысл в моем случае это делать?
источник
Ответы:
Мой предпочтительный способ избежать наследования класса 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)
Композиция - это действенная и разумная стратегия, которую вы также должны учитывать.
И если вам интересно, что означает запечатанная черта - это то, что может быть расширено только в том же файле. То есть два вышеуказанных класса case должны находиться в одном файле. Это позволяет выполнять исчерпывающие проверки компилятора:
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 в их собственные файлы. И, возможно, пойти с композицией.
источник
def name
черта характера должна бытьval name
. Мой компилятор выдавал мне предупреждения о недоступности кода с первым.Классы case идеально подходят для объектов значений, то есть объектов, которые не изменяют никаких свойств и могут быть сравнены с равными.
Но реализовать равные при наличии наследования довольно сложно. Рассмотрим два класса:
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
источник
Employer.fire(e: Emplooyee)
но не наоборот. Я хотел бы создать два разных класса, поскольку они на самом деле представляют разные объекты, но мне также не нравится возникающее повторение.В этих ситуациях я предпочитаю использовать композицию вместо наследования, т.е.
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.
источник