从技术的角度来看,混搭性能可以扩展案例类吗?

我多次读到SO,认为案件类不得延长,因为案件类别默认实行平等方式,导致平等的问题。 但是,如果一个特质扩展了一个case类,那么这也是有问题的吗?

case class MyCaseClass(string: String) trait MyTrait extends MyCaseClass val myCT = new MyCaseClass("hi") with MyTrait 

我想这个问题归结为MyTrait是否只能被混合到MyCaseClass的实例中,或者MyTrait是否继承MyTrait的类成员(字段值和方法),从而覆盖它们。 在第一种情况下,从MyCaseClass继承将是可以的,在后一种情况下,它不会好起来的。 但是哪一个呢?

为了调查,我推进了我的实验

 trait MyTrait extends MyCaseClass { def equals(m: MyCaseClass): Boolean = false def equals(m: MyCaseClass with MyTrait): Boolean = false } val myC = new MyCaseClass("hi") myCT.equals(myC) // res0: Boolean = true 

让我相信使用MyCaseClass的平等,而不是MyTrait的。 这就意味着一个特质可以扩展一个case类是可以的(尽管一个类不能扩展一个case类)。

但是,我不确定我的实验是否合法。 你能否就这个问题提出一些看法?

基本上,特质可以扩展任何类 ,所以最好在常规类中使用它们(OOP风格)。

无论如何,无论你的技巧如何,你的等价契约仍然被打破(注意,标准的Java的等equalsAny上定义的,默认情况下我们用HashMap甚至== ):

 scala> trait MyTrait extends MyCaseClass { | override def equals(m: Any): Boolean = false | } defined trait MyTrait scala> val myCT = new MyCaseClass("hi") with MyTrait myCT: MyCaseClass with MyTrait = MyCaseClass(hi) scala> val myC = new MyCaseClass("hi") myC: MyCaseClass = MyCaseClass(hi) scala> myC.equals(myCT) res4: Boolean = true scala> myCT.equals(myC) res5: Boolean = false 

此外, Hashcode/equals不是唯一的原因…

用另一个类扩展case class是不自然的,因为case class代表ADT,所以它只模拟数据 – 而不是行为。

这就是为什么你不应该添加任何方法(在OOD方面, case class是为贫血的方法而设计的)。 所以,在消除方法之后 – 一个只能和你的类混在一起的特性就变成了无稽之谈,因为与case类一起使用traits的要点是对disjunction进行建模(所以特性就是这里的接口 – 而不是混合):

 //your data model (Haskell-like): data Color = Red | Blue //Scala trait Color case object Red extends Color case object Blue extends Color 

如果Color只能和Blue混在一起 – 和

 data Color = Blue 

即使你需要更复杂的数据,比如

 //your data model (Haskell-like): data Color = BlueLike | RedLike data BlueLike = Blue | LightBlue data RedLike = Red | Pink //Scala trait Color extends Red trait BlueLike extends Color trait RedLike extends Color case class Red(name: String) extends RedLike //is OK case class Blue(name: String) extends BlueLike //won't compile!! 

绑定的Color是唯一的Red似乎并不是一个好的办法(一般),因为你将无法case object Blue extends BlueLike

PS案例类不打算用于OOP风格(混入是OOP的一部分) – 它们与类型类/模式匹配更好地交互。 所以我建议把你的复杂的方法类逻辑从case类中移走。 一种方法可能是:

 trait MyCaseClassLogic1 { def applyLogic(cc: MyCaseClass, param: String) = {} } trait MyCaseClassLogic2 extends MyCaseClassLogic { def applyLogic2(cc: MyCaseClass, param: String) = {} } object MyCaseClassLogic extends MyCaseClassLogic1 with MyCaseClassLogic2 

你可以在这里使用self-type或者trait extends ,但是你可以很容易地注意到它是多余的,因为applyLogic只绑定到MyCaseClass 🙂

另一种方法是implicit class (或者你可以尝试更类似类型的高级东西)

 implicit class MyCaseClassLogic(o: MyCaseClass) { def applyLogic = {} } 

PS2贫血与丰富。 ADT不是精确的贫血模型,因为它适用于不可变(无状态)的数据。 如果你阅读这篇文章的话 ,Martin Fowler的方法就是OOP / OOD,它默认是有状态的 – 这就是他在文章的大部分部分所暗示的,暗示服务层和业务层应该有不同的状态。 在FP中(至少在我的实践中),我们仍然将领域逻辑与服务逻辑分开,但是我们也将操作与数据(每一层)分开,这是另一回事。

扩展案例类是一个不好的做法(通常),因为它具有实际意义 – “数据容器”(POJO / ADT)。 例如,Kotlin不允许这样做。

另外,如果你真的想要一些特征来扩展case类,你最好使用requires依赖(以避免类继承的情况):

 scala> case class A() defined class A scala> trait B { self: A => } defined trait B scala> new B{} <console>:15: error: illegal inheritance; self-type B does not conform to B's selftype B with A new B{}