Tengo una aplicación basada en Squeryl. Defino mis modelos como clases de casos, principalmente porque encuentro 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 base de datos. 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 un if. Esto es molesto y no del todo seguro.
Lo que me gustaría hacer es factorizar el comportamiento y los campos comunes en una clase de caso ancestro y que los dos modelos reales hereden de ella. Pero, hasta donde tengo entendido, heredar de clases de casos está mal visto en Scala, e incluso está prohibido si la subclase es en sí misma una clase de casos (no es mi caso).
¿Cuáles son los problemas y las trampas que debería tener en cuenta al heredar de una clase de caso? ¿Tiene sentido en mi caso hacerlo?
fuente
Respuestas:
Mi forma preferida de evitar la herencia de clases de casos sin duplicación de código es algo obvia: 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 desea ser más detallado, 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
fuente
Dado que este es un tema interesante para muchos, permítanme arrojar algo de luz aquí.
Podrías optar por 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 es necesario duplicar métodos / funciones.
Si la duplicación de algunas propiedades es tan importante para usted, utilice clases regulares, pero recuerde que no se ajustan bien a FP.
Alternativamente, puede usar la composición en lugar de la 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 también debes considerar.
Y en caso de que se pregunte qué significa un rasgo sellado, es algo que solo se puede extender en el mismo archivo. Es decir, las dos clases de casos anteriores deben estar en el mismo archivo. Esto permite realizar 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 que es realmente útil. Ahora no te olvidarás de tratar con los otros tipos de
Person
s (personas). Esto es esencialmente lo que hace laOption
clase en Scala.Si eso no le importa, puede hacerlo sin sellar y colocar las clases de casos en sus propios archivos. Y quizás ir con la composición.
fuente
def name
rasgo in the debe serval name
. Mi compilador me estaba dando advertencias de código inalcanzable con el primero.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. Considere 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) debería 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 Point (1,4), ¿verdad? Pero, por supuesto, ColorPoint (1,4, rojo) no debería ser igual a ColorPoint (1,4, azul), porque tienen colores diferentes. Ahí lo tienes, una propiedad básica de la relación de igualdad se rompe.
actualizar
Puede usar la herencia de rasgos para resolver muchos problemas como se describe en otra respuesta. Una alternativa aún más flexible es a menudo utilizar clases de tipos. Consulte ¿ Para qué son útiles las clases de tipos en Scala? o http://www.youtube.com/watch?v=sVMES4RZF-8
fuente
Employer.fire(e: Emplooyee)
pero no al revés. Me gustaría hacer dos clases diferentes, ya que en realidad representan objetos diferentes, pero tampoco me gusta la repetición que surge.En estas situaciones, tiendo a usar 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, puede usar una jerarquía y coincidencias más sofisticadas, pero es de esperar que esto le dé una idea. La clave es aprovechar los extractores anidados que proporcionan las clases de casos
fuente