Christmas Cake pattern

Posted by Jan Machacek

Find me on:

06-Dec-2012 09:20:09

I had a lot of questions yesterday about the use of the cake pattern. (No, we have not invented it; we started Cake Solutions 12 years ago, long before Scala.) Let me explain the differences between the cake pattern, traditional DI and inheritance.

Why Christmas? Well, imagine you're modelling toy that needs batteries. It does not make sense to have the toy as a subtype of batteries. In code, it would be this horror:

trait Batteries

class AABatteries extends Batteries

// the Toy is actually a pack of AA batteries (!)
class Toy extends AABatteries

val toy = new Toy

Unless your toy is actually a pack of AA batteries, this code does not model reality well.

Turning to traditional DI, that is, dependency injection of instances, you would modify the toy to accept some instance of Batteries; and you would be responsible for creating this Batteries instance.

trait Batteries

class AABatteries extends Batteries

// the Toy needs an instance of Batteries
class Toy(batteries: Batteries)

val batteries = new AABatteries
val toy = new Toy(batteries)

This is better. We correctly separate the Toy and Batteries instances and we clearly say that Toy depends on some externally provided batteries. In this model, though, you might also be able to share the Batteries instance between multiple toys. This may or may not be what you actually want, so think carefully!

If all that you need is the functionality provided by the Batteries and you don't actually care about the instances, consider using the cake pattern. In the cake pattern, the injection takes the form of when you make an instance of Toy, you must supply the implementation of Batteries. In code, this is simply:

trait Batteries

class AABatteries extends Batteries

// When constructing instance of the toy, supply the batteries
class Toy {
  this: Batteries =>

}

val toy = new (Toy with AABatteries)

So, that's all the cake pattern does. It expresses type dependencies that must be satisfied when making instances of the dependant types. The compiler checks that you've satisfied the dependencies correctly. It is not injection of instances, it is injection of functionality. Let me rephrase that: in the DI of instances, you make an instance of the dependency and then supply the instance when constructing the dependant instance; in the DI of functionality, when you make an instance of the dependant type, you get the compiler to inject the functionality of the dependant type.

Subscribe to Email Updates