Reputation: 15557
I have the following design issue:
/**
* Those 2 traits are the public API
*/
trait Box {
def include(t: Token): Box
}
trait Token
/**
* Implementation classes
*/
case class BoxImpl(id: Int) extends Box {
/**
* the implementation of this method depends on the implementation
* of the Token trait
* TODO: REMOVE asInstanceOf
*/
def include(t: Token) = BoxImpl(t.asInstanceOf[TokenImpl].id + id)
}
case class TokenImpl(id: Int) extends Token
// use case
val b: Box = new BoxImpl(3)
val o: Token = new TokenImpl(4)
// == BoxImpl(7)
b.include(o)
In the code above, I don't want to expose id
in the public API (not even set it as private[myproject]
because that would still include circular dependencies in my project).
What would be the way to leave the public API intact while having the implementation classes having some visibility to each other (without the ugly cast)?
Upvotes: 2
Views: 107
Reputation: 39577
Type param or member:
trait Box {
type T <: Token
def include(t: T): Box
//def include(t: Token): Box
}
trait Token
case class BoxImpl(id: Int) extends Box {
type T = TokenImpl
def include(t: T) = BoxImpl(t.id + id)
/*
def include(t: Token) = t match {
case ti: TokenImpl => BoxImpl(ti.id + id)
case _ => throw new UnsupportedOperationException
}
*/
//def include(t: Token) = BoxImpl(t.asInstanceOf[TokenImpl].id + id)
}
case class TokenImpl(id: Int) extends Token
There's lots of ways to slice and dice it, hence the cake pattern, the proverbial piece of cake:
trait Boxer {
type Token <: Tokenable
trait Box {
def include(t: Token): Box
}
trait Tokenable
}
trait Boxed extends Boxer {
type Token = TokenImpl
case class BoxImpl(id: Int) extends Box {
override def include(t: Token) = BoxImpl(t.id + id)
}
case class TokenImpl(id: Int) extends Tokenable
}
trait User { this: Boxer =>
def use(b: Box, t: Token): Box = b.include(t)
}
object Test extends App with Boxed with User {
val b: Box = new BoxImpl(3)
val o: Token = new TokenImpl(4)
println(use(b, o))
}
Upvotes: 5