I'm currently currently using the Cake Pattern to implement some optimization algorithms. I often hit name collision problems. For instance:
trait Add[T] { this: Foo[T] =>
def constant: T
def plus( t1: T, t2: T ): T
def add( t: T ) = plus( t, constant )
}
trait Mul[T] { this: Bar[T] =>
def constant: T
def times( t1: T, t2: T ): T
def mul( t: T ) = times( t, constant )
}
trait Operations[T] { this: Add[T] with Mul[T] =>
def neg( t: T ): T
}
Here, constant
is defined in both Add
and Mul
traits, but their values could be different. I could prefix the name with the trait name but I find it ugly and brittle ( def mulConstant: T
). Is there a better way of doing it ?
To my best knowledge, the traditional cake pattern usually involves 1 layer of trait nesting, to group operations together. Then, the outer layer declares the actual "service" (here: Add, Mul, Operations) without defining it.
trait AddComponent[T] { this: FooComponent[T] =>
def addition: Add
trait Add {
def constant: T
def plus( t1: T, t2: T ): T
def add( t: T ) = plus( t, constant )
}
}
trait MulComponent[T] { this: BarComponent[T] =>
def multiplication: Mul
trait Mul {
def constant: T
def times( t1: T, t2: T ): T
def mul( t: T ) = times( t, constant )
}
}
trait OperationsComponent[T] { this: Add[T] with Mul[T] =>
def operations: Operations
trait Operations {
def neg( t: T ): T
}
}
Then, when mixing the "...Component" traits together, the dependencies are wired:
trait IntOperations extends Operation[Int] {
class IntAdd extends Add { ... }
class IntMul extends Mul { ... }
}
class MyFooBar extends FooComponent[Int] with BarComponent[Int] with IntOperations {
lazy val addition = new IntAdd
lazy val multiplication = new IntMul
lazy val foo = ...
lazy val bar = ...
}
This solves your particular namespacing problem but name clashes (of "service" definitions) remain a problem of the traditional cake pattern. There is a blog post by Daniel Spiewak demonstrating how that can be solved in general but the solution comes with its own set of (huge) tradeoffs (see this talk ).
Hope that helped a bit.
PS instead of type parameters it might be better to use abstract types here
This may be a bit unfashionable to say, and it's not a direct answer to your question, but isn't it easier to simply do dependency injection into constructors? Each collaborator has its own namespace so there's never a clash. And there's no problem either with the public api of the class. However, it remains hard to intermix the DI and cake patterns.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.