I am trying to understand and incorporate upper bound types with overriding in my system, but have not been able to achieve it without some ugly code. I have the following 2 traits:
trait MyId {
def getType: Int // For simplicity. This is Enum/Object in code.
def id: Any
}
trait MyTrait {
protected def supportedType: Int
def doSomething(id: MyId): Unit = {
if (id.getType == myType)
doSomethingInternal(id)
}
protected def doSomethingInternal(id: _ <: MyId): Unit
}
and I want to create subtypes as:
class X(x: Long) extends MyId {
override def getType: Int = 1
override def id: Long = x
}
class Y(y: String) extends MyId {
override def getType: Int = 2
override def id: String = y
}
class A extends MyTrait {
override protected def supportedType: Int = 1
override protected def doSomethingInternal(id: X): Unit {...}
}
class B extends MyTrait {
override protected def supportedType: Int = 2
override protected def doSomethingInternal(id: Y): Unit {...}
}
However, this does not work. The workaround I have been using is to use asInstanceOf[]
:
class A extends MyTrait {
override protected def supportedType: Int = 1
override protected def doSomethingInternal(id: MyId): Unit {
val id2 = id.asInstanceOf[X]
}
}
class B extends MyTrait {
override protected def supportedType: Int = 2
override protected def doSomethingInternal(id: MyId): Unit {
val id2 = id.asInstanceOf[Y]
}
}
Is there a way I can get rid of the asInstanceOf[]
?
EDIT
Further more, I have the following processor which requires calling the correct MyTrait
subclass based on the given MyId.getType
:
def process(ids: Seq[MyId], processors: Map[Int, MyTrait]): Unit = {
ids.foreach{ id =>
processors.get(id.getType).doSomething(id)
}
}
Edit 2:
getType
isInt
for simplicity here. This is actually an enum/object in my code.- Renamed
getSomething
todoSomething
for more clarity.
CodePudding user response:
It's not clear from the question if there's a specific need for the indirection between getSomething
and getSomethingInternal
other than the getType
consistency check, but since it looks like the goal is just to prevent calling getSomething
with the wrong id type, it seems to me it'd be simpler to define MyTrait with a type parameter for the expected id:
trait MyTrait[T] {
def getSomething(id: T): Unit
}
class A extends MyTrait[Long] {
def getSomething(id: Long): Unit = { println(s"long id: $id") }
}
class B extends MyTrait[String] {
def getSomething(id: String): Unit = { println(s"string id: $id") }
}
Then things look like this:
val a = A()
val b = B()
a.getSomething(123)
// prints "long id: 123"
b.getSomething("abc")
// prints "string id: abc"
a.getSomething("bad") // <--- doesn't compile
b.getSomething(123) // <--- doesn't compile
CodePudding user response:
It is not entirely clear to me what it is exactly that you are trying to do, but, perhaps, something like this will work. The key is to use Type
for a type, and not attempt to encode it as an Int
... and then convert to a type.
trait MyId[Type] {
def id: Type
}
trait MyTrait[T] {
def getSomething(id: MyId[T]): Unit =
getSomethingInternal(id.id)
protected def getSomethingInternal(id: T): Unit
}
Again, this was largely a shot in the dark, because I have no idea why your types are number or why your gets
return Unit ... among other things.
Update
Well, ok, if your type is an "enum", you wanna make your MyId
a sealed trait:
sealed trait MyId[T] {
def id: T
}
case class X(id: Long) extends MyId[Long]
case class Y(id: String) extends MyId[String]
But now you want to process your ids uniformely. If the list of processors is static, just do match
:
def process(ids: Seq[MyId[_]]) = ids.map {
case x: X => processX
case y: Y => process Y
}
And if it isn't? Well .. in that case, I'd start with another upda te to your question, explaining why you need it, and how you generate that dynamic list.