J'ai une application basée sur Squeryl. Je définis mes modèles comme des classes de cas, surtout parce que je trouve pratique d'avoir des méthodes de copie.
J'ai deux modèles strictement liés. Les champs sont identiques, de nombreuses opérations sont communes et doivent être stockées dans la même table DB. Mais il y a un comportement qui n'a de sens que dans l'un des deux cas, ou qui a du sens dans les deux cas mais qui est différent.
Jusqu'à présent, je n'ai utilisé qu'une seule classe de cas, avec un indicateur qui distingue le type de modèle, et toutes les méthodes qui diffèrent en fonction du type de modèle commencent par un if. C'est ennuyeux et pas tout à fait sûr.
Ce que je voudrais faire est de factoriser le comportement et les champs communs dans une classe de cas ancêtre et que les deux modèles réels en héritent. Mais, pour autant que je sache, hériter des classes de cas est mal vu dans Scala, et est même interdit si la sous-classe est elle-même une classe de cas (pas mon cas).
Quels sont les problèmes et les pièges dont je dois être conscient en héritant d'une classe de cas? Est-ce que cela a du sens dans mon cas de le faire?
la source
Réponses:
Ma façon préférée d'éviter l'héritage de classe de cas sans duplication de code est quelque peu évidente: créer une classe de base commune (abstraite):
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 vous voulez être plus précis, regroupez les propriétés en traits individuels:
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
la source
Puisqu'il s'agit d'un sujet intéressant pour beaucoup, permettez-moi de faire la lumière ici.
Vous pouvez opter pour l'approche suivante:
// 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
Oui, vous devez dupliquer les champs. Sinon, il ne serait tout simplement pas possible d'implémenter une égalité correcte parmi d'autres problèmes .
Cependant, vous n'avez pas besoin de dupliquer les méthodes / fonctions.
Si la duplication de quelques propriétés est si importante pour vous, utilisez des classes régulières, mais rappelez-vous qu'elles ne correspondent pas bien à FP.
Vous pouvez également utiliser la composition au lieu de l'héritage:
case class Employee( person: Person, salary: Int ) // In code: val employee = ... println(employee.person.name)
La composition est une stratégie valable et solide que vous devriez également considérer.
Et au cas où vous vous demandez ce que signifie un trait scellé, c'est quelque chose qui ne peut être étendu que dans le même fichier. Autrement dit, les deux classes de cas ci-dessus doivent être dans le même fichier. Cela permet des vérifications exhaustives du compilateur:
val x = Employee(name = "Jack", salary = 50000) x match { case Employee(name) => println(s"I'm $name!") }
Donne une erreur:
warning: match is not exhaustive! missing combination Tourist
Ce qui est vraiment utile. Maintenant, vous n'oublierez pas de vous occuper des autres types de
Person
s (personnes). C'est essentiellement ce que fait laOption
classe dans Scala.Si cela ne vous importe pas, vous pouvez le rendre non scellé et jeter les classes de cas dans leurs propres fichiers. Et peut-être aller avec la composition.
la source
def name
dans le trait doit êtreval name
. Mon compilateur me donnait des avertissements de code inaccessibles avec l'ancien.les classes de cas sont parfaites pour les objets de valeur, c'est-à-dire les objets qui ne changent aucune propriété et peuvent être comparés à des égaux.
Mais la mise en œuvre d'égaux en présence d'héritage est assez compliquée. Considérez deux classes:
class Point(x : Int, y : Int)
et
class ColoredPoint( x : Int, y : Int, c : Color) extends Point
Donc, selon la définition, le ColorPoint (1,4, rouge) doit être égal au point (1,4), ils sont le même point après tout. Donc ColorPoint (1,4, bleu) devrait également être égal à Point (1,4), non? Mais bien sûr, ColorPoint (1,4, rouge) ne doit pas être égal à ColorPoint (1,4, bleu), car ils ont des couleurs différentes. Voilà, une propriété fondamentale de la relation d'égalité est rompue.
mise à jour
Vous pouvez utiliser l'héritage des traits pour résoudre de nombreux problèmes, comme décrit dans une autre réponse. Une alternative encore plus flexible consiste souvent à utiliser des classes de types. Voir À quoi servent les classes de types dans Scala? ou http://www.youtube.com/watch?v=sVMES4RZF-8
la source
Employer.fire(e: Emplooyee)
mais pas l'inverse. Je voudrais créer deux classes différentes, car elles représentent en fait des objets différents, mais je n'aime pas non plus la répétition qui en résulte.Dans ces situations, j'ai tendance à utiliser la composition au lieu de l'héritage, c'est-à-dire
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") }
Évidemment, vous pouvez utiliser une hiérarchie et des correspondances plus sophistiquées, mais j'espère que cela vous donnera une idée. La clé est de tirer parti des extracteurs imbriqués fournis par les classes de cas
la source