从技术的角度来看,混搭性能可以扩展案例类吗?
我多次读到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的等equals
在Any
上定义的,默认情况下我们用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{}