ChatGPT解决这个技术问题 Extra ChatGPT

Scala案例类继承

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

我有两个严格相关的模型。字段是一样的,很多操作是通用的,要存放在同一个DB表中。但是有些行为只在两种情况中的一种情况下才有意义,或者在两种情况下都有意义但不同。

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

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

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

你不能从非案例类继承,或者扩展一个共同特征吗?
我不知道。这些字段在祖先中定义。我想根据这些字段获取复制方法、相等等。如果我将父级声明为抽象类,将子级声明为案例类,是否会考虑父级上定义的参数?
我认为不是,您必须在抽象父(或特征)和目标案例类中定义道具。最后,很多样板,但至少类型安全

M
Malte Schwerhoff

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

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

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

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

您所说的“没有代码重复”在哪里?是的,在案例类及其父级之间定义了合同,但您仍在输入道具 X2
@virtualeyes 是的,您仍然需要重复这些属性。但是,您不必重复方法,这通常意味着比属性更多的代码。
是的,只是希望解决属性的重复问题——另一个答案暗示类型类是一种可能的解决方法;然而,不确定如何似乎更倾向于混合行为,如特征,但更灵活。只是样板:案例类,可以忍受它,如果不是这样的话,那将是非常不可思议的,真的可以破解大量的属性定义
@virtualeyes 我完全同意,如果可以以简单的方式避免属性重复,那就太好了。编译器插件肯定可以解决问题,但我不会称其为简单方法。
@virtualeyes 我认为避免代码重复不仅仅是少写。对我来说,更重要的是在应用程序的不同部分中没有相同的代码,它们之间没有任何联系。使用此解决方案,所有子类都绑定到契约,因此如果父类发生更改,IDE 将能够帮助您识别需要修复的代码部分。
K
Kai Sellgren

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

您可以采用以下方法:

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

是的,您必须复制这些字段。如果不这样做,就不可能实现正确的相等 among other problems

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

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

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

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

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

作曲是您也应该考虑的有效且合理的策略。

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

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

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

给出一个错误:

warning: match is not exhaustive!
missing combination            Tourist

这真的很有用。现在您不会忘记处理其他类型的Person(人)。这本质上是 Scala 中的 Option 类所做的。

如果这对您来说无关紧要,那么您可以将其设为非密封并将案例类放入他们自己的文件中。也许还有作文。


我认为特征中的 def name 需要是 val name。我的编译器用前者给了我无法访问的代码警告。
C
Community

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

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

class Point(x : Int, y : Int)

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

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

更新

您可以使用从特征继承来解决许多问题,如另一个答案中所述。一个更灵活的选择通常是使用类型类。请参阅 What are type classes in Scala useful for?http://www.youtube.com/watch?v=sVMES4RZF-8


我理解并同意这一点。所以,当你有一个处理雇主和雇员的应用程序时,你建议应该做什么。假设它们共享所有字段(姓名、地址等),唯一的区别在于某些方法 - 例如,可能想要定义 Employer.fire(e: Emplooyee) 而不是相反。我想做两个不同的类,因为它们实际上代表不同的对象,但我也不喜欢出现的重复。
这里有一个类型类方法的例子吗?即关于案例类别
@virtualeyes 可以为各种实体拥有完全独立的类型,并提供类型类来提供行为。这些类型类可以尽可能多地使用继承,因为它们不受案例类的语义契约的约束。对这个问题有用吗?不知道,这个问题不够具体,无法说明。
@JensSchauder 似乎特征在行为方面提供了相同的东西,只是不如类型类灵活;我正在了解案例类属性的不重复,这是特征或抽象类通常会帮助人们避免的事情。
佚名

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

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

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


这似乎是这里唯一真正没有重复字段的答案