Scala case class inheritance

Mam wniosek oparty na Squeryl. Definiuję moje modele jako klasy przypadków, głównie dlatego, że uważam, że wygodne są metody kopiowania.

Mam dwa modele, które są ściśle ze sobą powiązane. Pola są takie same, wiele operacji jest wspólnych i mają być przechowywane w tej samej tabeli DB. Ale jest pewne zachowanie, które ma sens tylko w jednym z dwóch przypadków, lub które ma sens w obu przypadkach, ale jest inne.

Do tej pory używałem tylko jednej klasy case, Z flaga, która odróżnia typ modelu, a wszystkie metody, które różnią się w zależności od typu modelu, zaczynają się od if. Jest to denerwujące i nie do końca bezpieczne.

To, co chciałbym zrobić, to wziąć pod uwagę wspólne zachowanie i pola w klasie przypadku przodka i mieć dwa rzeczywiste modele dziedziczą z niej. Ale, o ile rozumiem, dziedziczenie po klasach case jest źle widziane w Scali i jest nawet zabronione, jeśli podklasa sama w sobie jest klasą case (Nie moim przypadkiem).

Jakie są problemy i pułapki, których powinienem być świadomy przy dziedziczeniu po klasie case? Czy to ma sens w moim przypadku?

Author: Andrea, 2012-10-03

4 answers

Mój preferowany sposób unikania dziedziczenia klas case bez powielania kodu jest dość oczywisty: Utwórz wspólną (abstrakcyjną) klasę bazową:

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


Jeśli chcesz być bardziej drobnoziarnisty, pogrupuj właściwości w poszczególne cechy:

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
 96
Author: Malte Schwerhoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-03 12:28:03

Ponieważ jest to interesujący temat dla wielu, pozwól mi rzucić trochę światła tutaj.

Można zastosować następujące podejście:

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

Tak, musisz zduplikować pola. Jeśli tego nie zrobisz, po prostu nie będzie możliwe zaimplementowanie poprawnej równości wśród innych problemów .

Nie musisz jednak powielać metod/funkcji.

Jeśli powielanie kilku właściwości jest dla ciebie tak ważne, użyj zwykłych klas, ale pamiętaj, że nie pasują do FP.

Alternatywnie możesz użyć kompozycji zamiast dziedziczenia:

case class Employee(
  person: Person,
  salary: Int
)

// In code:
val employee = ...
println(employee.person.name)

Kompozycja jest ważną i solidną strategią, którą również powinieneś rozważyć.

A jeśli zastanawiasz się, co oznacza cecha zapieczętowana - jest to coś, co można rozszerzyć tylko w tym samym pliku. Oznacza to, że dwie powyższe klasy przypadków muszą znajdować się w tym samym pliku. Pozwala to na wyczerpujące sprawdzenie kompilatora:

val x = Employee(name = "Jack", salary = 50000)

x match {
  case Employee(name) => println(s"I'm $name!")
}

Daje błąd:

warning: match is not exhaustive!
missing combination            Tourist

Który jest naprawdę przydatne. Teraz nie zapomnisz poradzić sobie z innymi typami Person s (ludzie). Jest to zasadniczo to, co robi klasa Option w Scali.

Jeśli to nie ma dla ciebie znaczenia, możesz uczynić go niezabezpieczonym i wrzucić klasy spraw do ich własnych plików. I być może iść z kompozycji.

 38
Author: Kai Sellgren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-11 19:49:57

Klasy Case są idealne dla obiektów value, tzn. obiektów, które nie zmieniają żadnych właściwości i mogą być porównywane z równymi.

Ale implementacja równości w obecności dziedziczenia jest dość skomplikowana. Rozważmy dwie klasy:

class Point(x : Int, y : Int)

I

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

Więc zgodnie z definicją punkt koloru (1,4, czerwony) powinien być równy punktowi (1,4) są przecież tym samym punktem. Więc ColorPoint(1,4, niebieski) również powinien być równy punktowi (1,4), prawda? Ale oczywiście ColorPoint (1,4, czerwony) nie powinien być równy ColorPoint (1,4, niebieski), ponieważ mają różne kolory. Proszę bardzo, jedna z podstawowych własności relacji równości jest złamana.

Update

Możesz użyć dziedziczenia z cech rozwiązując wiele problemów, jak opisano w innej odpowiedzi. Jeszcze bardziej elastyczną alternatywą jest często używanie klas typu. Zobacz do czego są przydatne klasy typów w Scali? lub http://www.youtube.com/watch?v=sVMES4RZF-8

 12
Author: Jens Schauder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:10

W takich sytuacjach zazwyczaj używam kompozycji zamiast dziedziczenia tzn.

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

Oczywiście możesz użyć bardziej wyrafinowanej hierarchii i dopasowań, ale mam nadzieję, że to da ci pomysł. Kluczem jest skorzystanie z zagnieżdżonych ekstraktorów, które klasy case dostarczają

 4
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-08 15:36:41