Eric
Eric

Reputation: 15557

Design issue with dependent traits and dependent implementations

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

Answers (1)

som-snytt
som-snytt

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

Related Questions