scala Scala案例类继承

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12705309/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:36:07  来源:igfitidea点击:

Scala case class inheritance

scalainheritancecase-class

提问by Andrea

I have an application based on Squeryl. I define my models as case classes, mostly since I find convenient to have copy methods.

我有一个基于 Squeryl 的应用程序。我将我的模型定义为案例类,主要是因为我发现使用复制方法很方便。

I have two models that are strictly related. The fields are the same, many operations are in common, and they are to be stored in the same DB table. Butthere is some behaviour that only makes sense in one of the two cases, or that makes sense in both cases but is different.

我有两个严格相关的模型。字段相同,许多操作是相同的,并且要存储在同一个DB表中。但是有些行为仅在两种情况之一中才有意义,或者在两种情况下都有意义但不同。

Until now I only have used a single case class, with a flag that distinguishes the type of the model, and all methods that differ based on the type of the model start with an if. This is annoying and not quite type safe.

到目前为止,我只使用了一个 case 类,带有一个区分模型类型的标志,所有基于模型类型而不同的方法都以 if 开头。这很烦人,而且不太安全。

What I would like to do is factor the common behaviour and fields in an ancestor case class and have the two actual models inherit from it. But, as far as I understand, inheriting from case classes is frowned upon in Scala, and is even prohibited if the subclass is itself a case class (not my case).

我想要做的是考虑祖先案例类中的常见行为和字段,并让两个实际模型从中继承。但是,据我所知,从案例类继承在 Scala 中是不受欢迎的,如果子类本身就是案例类(不是我的情况),甚至是禁止的。

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?

从案例类继承时我应该注意哪些问题和陷阱?在我的情况下这样做有意义吗?

回答by Malte Schwerhoff

My preferred way of avoiding case class inheritance without code duplication is somewhat obvious: create a common (abstract) base class:

我在没有代码重复的情况下避免案例类继承的首选方法有些明显:创建一个公共(抽象)基类:

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


If you want to be more fine-grained, group the properties into individual traits:


如果您想要更细粒度,请将属性分组为单个特征:

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

回答by Kai Sellgren

Since this is an interesting topic to many, let me shed some light here.

由于这对许多人来说是一个有趣的话题,让我在这里说明一下。

You could go with the following approach:

您可以采用以下方法:

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

Yes, you have to duplicate the fields. If you don't, it simply would not be possible to implement correct equality among other problems.

是的,您必须复制这些字段。如果你不这样做,就不可能在其他问题中实现正确的平等。

However, you don't need to duplicate methods/functions.

但是,您不需要复制方法/函数。

If the duplication of a few properties is that much of an importance to you, then use regular classes, but remember that they don't fit FP well.

如果一些属性的重复对您来说非常重要,那么请使用常规类,但请记住,它们不太适合 FP。

Alternatively, you could use composition instead of inheritance:

或者,您可以使用组合而不是继承:

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

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

Composition is a valid and a sound strategy that you should consider as well.

组合是一种有效且合理的策略,您也应该考虑。

And in case you wonder what a sealed trait means — it is something that can be extended only in the same file. That is, the two case classes above have to be in the same file. This allows for exhaustive compiler checks:

如果您想知道密封特征是什么意思——它只能在同一个文件中扩展。也就是说,上面的两个案例类必须在同一个文件中。这允许进行详尽的编译器检查:

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

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

Gives an error:

给出一个错误:

warning: match is not exhaustive!
missing combination            Tourist

Which is really useful. Now you won't forget to deal with the other types of Persons (people). This is essentially what the Optionclass in Scala does.

这真的很有用。现在您不会忘记处理其他类型的Persons(人)。这基本上就是OptionScala 中的类所做的。

If that does not matter to you, then you could make it non-sealed and throw the case classes into their own files. And perhaps go with composition.

如果这对您来说无关紧要,那么您可以将其设为非密封并将案例类放入它们自己的文件中。也许和组合一起去。

回答by Jens Schauder

case classes are perfect for value objects, i.e. objects that don't change any properties and can be compared with equals.

case 类非常适合值对象,即不改变任何属性并且可以与 equals 进行比较的对象。

But implementing equals in the presence of inheritance is rather complicated. Consider a two classes:

但是在存在继承的情况下实现 equals 是相当复杂的。考虑两个类:

class Point(x : Int, y : Int)

and

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

So according to the definition the ColorPoint(1,4,red) should be equal to the Point(1,4) they are the same Point after all. So ColorPoint(1,4,blue) should also be equal to Point(1,4), right? But of course ColorPoint(1,4,red) should not equal ColorPoint(1,4,blue), because they have different colors. There you go, one basic property of the equality relation is broken.

所以根据定义 ColorPoint(1,4,red) 应该等于 Point(1,4) 他们毕竟是同一个 Point。所以 ColorPoint(1,4,blue) 也应该等于 Point(1,4) 吧?但是当然 ColorPoint(1,4,red) 不应该等于 ColorPoint(1,4,blue),因为它们有不同的颜色。好了,等式关系的一个基本属性被破坏了。

update

更新

You can use inheritance from traits solving lots of problems as described in another answer. An even more flexible alternative is often to use type classes. See What are type classes in Scala useful for?or http://www.youtube.com/watch?v=sVMES4RZF-8

您可以使用继承来解决许多问题,如另一个答案中所述。更灵活的替代方法通常是使用类型类。请参阅Scala 中的类型类有什么用?http://www.youtube.com/watch?v=sVMES4RZF-8

回答by Jens Schauder

In these situations I tend to use composition instead of inheritance i.e.

在这些情况下,我倾向于使用组合而不是继承,即

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

Obviously you can use a more sophisticated hierarchy and matches but hopefully this gives you an idea. The key is to take advantage of the nested extractors that case classes provide

显然你可以使用更复杂的层次结构和匹配,但希望这能给你一个想法。关键是利用案例类提供的嵌套提取器