2012-10-03 21 views
69

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

Tengo dos modelos estrictamente relacionados. Los campos son los mismos, muchas operaciones son comunes y deben almacenarse en la misma tabla de DB. Pero existe 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 sola 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 que comience 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 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).

What are the problems and pitfalls I should be aware in inheriting from a case class? Does it make sense in my case to do so?

+1

¿No podría heredar de una clase que no sea de caso o extender un rasgo común? – Eduardo

+0

No estoy seguro. Los campos están definidos en el ancestro. Quiero obtener métodos de copia, igualdad, etc. basados ​​en esos campos. Si declaro el padre como una clase abstracta y los hijos como una clase de caso, ¿tendrá en cuenta los parámetros de cuentas definidos en el padre? – Andrea

+0

Creo que no, debe definir los accesorios tanto en el resumen padre (o rasgo) como en la clase de caso de destino. Al final, lot's o 'boilerplate, pero escriba safe al menos – virtualeyes

Respuesta

89

Mi forma preferida de evitar la herencia de clases caso sin duplicación de código es algo evidente: crear un (resumen) clase base común:

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 fino , agrupe 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 
+48

¿dónde está esto "sin duplicación de código" de lo que usted habla? Sí, se define un contrato entre la clase de caso y sus padres, pero todavía está escribiendo props X2 – virtualeyes

+2

@virtualeyes Cierto, todavía tiene que repetir las propiedades. Sin embargo, no es necesario repetir los métodos, que generalmente equivalen a más código que las propiedades. –

+1

Sí, solo esperaba solucionar la duplicación de propiedades; otra respuesta da pistas sobre las clases de tipos como una solución posible; no estoy seguro de cómo, sin embargo, parece estar más orientado a la mezcla en el comportamiento, como los rasgos, pero más flexible. Simplemente repetitivo: clases de caso, puede vivir con él, sería increíble si no fuera así, podría realmente piratear grandes secciones de definiciones de propiedad – virtualeyes

12

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

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

class Point(x : Int, y : Int) 

y

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

Así que de acuerdo a la definición del 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.

actualización

Puede utilizar la herencia de rasgos resolver un montón de problemas como se describe en otra respuesta. Una alternativa aún más flexible es a menudo utilizar clases de tipos. Ver What are type classes in Scala useful for? o http://www.youtube.com/watch?v=sVMES4RZF-8

+0

Entiendo y estoy de acuerdo con eso. Entonces, qué sugieres que se haga cuando tienes una aplicación que trata con, digamos, empleadores y empleados. Supongamos que comparten todos los campos (nombre, dirección, etc.), la única diferencia está en algunos métodos, por ejemplo, uno puede querer definir 'Employer.fire (e: Emplooyee)' pero no al revés. Me gustaría hacer dos clases diferentes, ya que en realidad representan diferentes objetos, pero tampoco me gusta la repetición que surge. – Andrea

+0

tiene un ejemplo de enfoque de clase de tipo con la pregunta aquí? es decir, en relación con las clases de casos – virtualeyes

+0

@virtualeyes Se podrían tener tipos completamente independientes para los diversos tipos de entidades y proporcionar clases de tipos para proporcionar el comportamiento. Estas clases de tipos podrían usar la herencia tanto como sea útil, ya que no están vinculadas por el contrato semántico de las clases de casos. ¿Sería útil en esta pregunta? No sé, la pregunta no es lo suficientemente específica como para contarla. –

36

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

Usted podría 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í, usted tiene que duplicar los campos. Si no lo hace, simplemente no sería posible implementar la igualdad correcta among other problems.

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

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

Como alternativa, puede utilizar la composición en lugar de la herencia:

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

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

composición es una estrategia válida y un sonido que se debe considerar también.

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

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 

que es realmente útil. Ahora no se olvidará de tratar con los otros tipos de Person s (personas). Esto es esencialmente lo que hace la clase Option en Scala.

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

+1

Creo que el 'nombre def 'en el rasgo debe ser' val name'. Mi compilador me estaba dando advertencias de código inalcanzables con el primero. – BAR

3

En estas situaciones que tienden a utilizar la composición en lugar de la 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 se puede utilizar una jerarquía y partidos más sofisticado pero espero que esto te da una idea. La clave es aprovechar los extractores anidados que las clases de casos proporcionan

Cuestiones relacionadas