Tenho um aplicativo baseado no Squeryl. Eu defino meus modelos como classes de caso, principalmente porque acho conveniente ter métodos de cópia.
Tenho dois modelos estritamente relacionados. Os campos são os mesmos, muitas operações são comuns e devem ser armazenadas na mesma tabela de banco de dados. Mas há algum comportamento que só faz sentido em um dos dois casos, ou que faz sentido nos dois casos, mas é diferente.
Até agora, usei apenas uma única classe de caso, com um sinalizador que distingue o tipo do modelo, e todos os métodos que diferem com base no tipo do modelo começam com um if. Isso é irritante e não é totalmente seguro para o tipo.
O que eu gostaria de fazer é fatorar o comportamento comum e os campos em uma classe de caso ancestral e ter os dois modelos reais herdados dela. Mas, até onde eu entendo, herdar de classes de caso é desaprovado em Scala e é até proibido se a própria subclasse for uma classe de caso (não é o meu caso).
Quais são os problemas e armadilhas que devo estar ciente ao herdar de uma classe de caso? Isso faz sentido no meu caso?
fonte
Respostas:
Minha maneira preferida de evitar a herança de classe de caso sem duplicação de código é um tanto óbvia: crie uma classe base comum (abstrata):
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
Se você quiser ser mais refinado, agrupe as propriedades em características individuais:
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
fonte
Uma vez que este é um tópico interessante para muitos, deixe-me lançar algumas luzes aqui.
Você poderia escolher a seguinte abordagem:
// 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
Sim, você deve duplicar os campos. Do contrário, simplesmente não seria possível implementar a igualdade correta entre outros problemas .
No entanto, você não precisa duplicar métodos / funções.
Se a duplicação de algumas propriedades é tão importante para você, use classes regulares, mas lembre-se de que elas não se encaixam bem no PF.
Como alternativa, você pode usar composição em vez de herança:
case class Employee( person: Person, salary: Int ) // In code: val employee = ... println(employee.person.name)
A composição é uma estratégia válida e sólida que você também deve considerar.
E caso você esteja se perguntando o que significa um traço selado - é algo que pode ser estendido apenas no mesmo arquivo. Ou seja, as duas classes de caso acima devem estar no mesmo arquivo. Isso permite verificações exaustivas do compilador:
val x = Employee(name = "Jack", salary = 50000) x match { case Employee(name) => println(s"I'm $name!") }
Dá um erro:
warning: match is not exhaustive! missing combination Tourist
O que é muito útil. Agora você não vai esquecer de lidar com os outros tipos de
Person
s (pessoas). Isso é essencialmente o que aOption
classe em Scala faz.Se isso não importa para você, pode torná-lo não lacrado e colocar as classes de caso em seus próprios arquivos. E talvez vá com composição.
fonte
def name
o traço precisa serval name
. Meu compilador estava me dando avisos de código inacessível com o anterior.classes de caso são perfeitas para objetos de valor, ou seja, objetos que não alteram nenhuma propriedade e podem ser comparados com iguais.
Mas implementar igual na presença de herança é bastante complicado. Considere duas classes:
class Point(x : Int, y : Int)
e
class ColoredPoint( x : Int, y : Int, c : Color) extends Point
Então de acordo com a definição o ColorPoint (1,4, red) deve ser igual ao Point (1,4) eles são o mesmo Point afinal. Então ColorPoint (1,4, blue) também deve ser igual a Point (1,4), certo? Mas é claro que ColorPoint (1,4, vermelho) não deve ser igual a ColorPoint (1,4, azul), porque eles têm cores diferentes. Aí está, uma propriedade básica da relação de igualdade foi quebrada.
atualizar
Você pode usar a herança de características resolvendo muitos problemas, conforme descrito em outra resposta. Uma alternativa ainda mais flexível é usar classes de tipo. Consulte Para que servem as classes de tipo em Scala? ou http://www.youtube.com/watch?v=sVMES4RZF-8
fonte
Employer.fire(e: Emplooyee)
mas não o contrário. Eu gostaria de fazer duas classes diferentes, pois na verdade elas representam objetos diferentes, mas também não gosto da repetição que surge.Nessas situações, tendo a usar composição em vez de herança, ou seja,
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, você pode usar uma hierarquia e correspondências mais sofisticadas, mas espero que isso lhe dê uma ideia. A chave é tirar vantagem dos extratores aninhados que as classes de caso fornecem
fonte