Basically, trait can extend any class, so it's better to use them with regular classes (OOP-style).
Anyway, your equals contract is still broken regardless of your trick (note that standard Java's equals
is defined on Any
, that is used by default let's say in HashMap
or even ==
):
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
Besides, Hashcode/equals
isn't the only reason...
Extending case class
with another class is unnatural because case class
represents ADT so it models only data - not behavior.
That's why you should not add any methods to it (in OOD terms case class
es are designed for anemic approach). So, after eliminating methods - a trait that can only be mixed with your class becomes nonsense as the point of using traits with case classes is to model disjunction (so traits are interfaces here - not mix-ins):
//your data model (Haskell-like):
data Color = Red | Blue
//Scala
trait Color
case object Red extends Color
case object Blue extends Color
If Color
could be mixed only with Blue
- it's same as
data Color = Blue
Even if you require more complex data, like
//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!!
binding Color
to be only Red
doesn't seem to be a good approach (in general) as you won't be able to case object Blue extends BlueLike
P.S. Case classes are not intended to be used in OOP-style (mix-ins are part of OOP) - they interact better with type-classes/pattern-matching. So I would recommend to move your complex method-like logic away from case class. One approach could be:
trait MyCaseClassLogic1 {
def applyLogic(cc: MyCaseClass, param: String) = {}
}
trait MyCaseClassLogic2 extends MyCaseClassLogic {
def applyLogic2(cc: MyCaseClass, param: String) = {}
}
object MyCaseClassLogic extends MyCaseClassLogic1 with MyCaseClassLogic2
You could use self-type or trait extends
here but you can easily notice that it's redundant as applyLogic
is bound to MyCaseClass
only :)
Another approach is implicit class
(or you can try more advanced stuff like type-classes)
implicit class MyCaseClassLogic(o: MyCaseClass) {
def applyLogic = {}
}
P.S.2 Anemic vs Rich. ADT
is not precisely anemic model as it applies to immutable (stateless) data. If you read the article, Martin Fowler's approach is OOP/OOD which is stateful by default - that's what he assumes in most of the part of his article by implying that service layer and business layer should have separate states. in FP (at least in my practice) we still separate domain logic from service-logic, but we also separate operations from data (in every layer), which is another matter.