Pregunta Herencia de clase de caso Scala


Tengo una aplicación basada en Squeryl. Defino mis modelos como clases de casos, principalmente porque considero conveniente tener métodos de copia.

Tengo dos modelos que están estrictamente relacionados. Los campos son los mismos, muchas operaciones son comunes y deben almacenarse en la misma tabla de DB. Pero hay algún comportamiento que solo tiene sentido en uno de los dos casos, o que tiene sentido en ambos casos, pero es diferente.

Hasta ahora solo he usado una única clase de caso, con una bandera que distingue el tipo de modelo, y todos los métodos que difieren según el tipo de modelo comienzan con if. Esto es molesto y no del todo seguro.

Lo que me gustaría hacer es factorizar el comportamiento común y los campos en una clase de caso ancestro y hacer heredar los dos modelos reales. Pero, por lo que yo entiendo, heredar de las clases de caso está mal visto en Scala, e incluso está prohibido si la subclase es en sí misma una clase de caso (no es mi caso).

¿Cuáles son los problemas y dificultades que debería tener en cuenta al heredar de una clase de caso? ¿Tiene sentido en mi caso hacerlo?


74
2017-10-03 09:13


origen


Respuestas:


Mi forma preferida de evitar la herencia de clase de caso sin duplicación de código es algo obvio: crear una clase base común (abstracta):

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


Si quieres ser más refinado, agrupa las propiedades en rasgos individuales:

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

94
2017-10-03 10:21



Dado que este es un tema interesante para muchos, permítanme arrojar algo de luz aquí.

Puedes ir con el siguiente enfoque:

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

Sí, tienes que duplicar los campos. Si no lo hace, simplemente no sería posible implementar la igualdad correcta entre otros problemas.

Sin embargo, no necesita duplicar métodos / funciones.

Si la duplicación de algunas propiedades es muy importante para usted, use clases regulares, pero recuerde que no se ajustan bien a FP.

Alternativamente, puede usar composición en lugar de herencia:

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

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

La composición es una estrategia válida y sólida que debes considerar también.

Y en caso de que se pregunte qué significa un rasgo sellado, es algo que puede extenderse solo en el mismo archivo. Es decir, las dos clases de casos anteriores deben estar en el mismo archivo. Esto permite comprobaciones exhaustivas del compilador:

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

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

Da un error:

warning: match is not exhaustive!
missing combination            Tourist

Lo cual es realmente útil. Ahora no olvidará tratar con los otros tipos de Persons (personas). Esto es esencialmente lo que Option clase en Scala hace.

Si eso no te importa, entonces podrías hacerlo no sellado y lanzar las clases de casos en sus propios archivos. Y tal vez vaya con la composición.


38
2017-10-14 11:18



las clases de casos son perfectas para objetos de valor, es decir, objetos que no cambian ninguna propiedad y se pueden comparar con iguales.

Pero implementar iguales en presencia de herencia es bastante complicado. Considera dos clases:

class Point(x : Int, y : Int)

y

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

Entonces, de acuerdo con la definición, el ColorPoint (1,4, rojo) debe ser igual al Punto (1,4), son el mismo Punto después de todo. Entonces, ColorPoint (1,4, azul) también debería ser igual a Punto (1,4), ¿verdad? Pero, por supuesto, ColorPoint (1,4, rojo) no debe ser igual a ColorPoint (1,4, azul), ya que tienen diferentes colores. Ahí va, se rompe una propiedad básica de la relación de igualdad.

actualizar

Puede usar la herencia de los rasgos resolviendo muchos problemas como se describe en otra respuesta. Una alternativa aún más flexible es a menudo utilizar clases de tipos. Ver ¿Para qué sirven las clases de tipos en Scala? o http://www.youtube.com/watch?v=sVMES4RZF-8


12
2017-10-03 09:33



En estas situaciones, tiendo a usar composición en lugar de herencia, es decir

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

Obviamente, puedes usar una jerarquía y combinaciones más sofisticadas, pero espero que esto te dé una idea. La clave es aprovechar los extractores anidados que proporcionan las clases de casos


4
2017-08-08 15:36