Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Cake Pattern Encourage Hardcoded Dependencies?

I'm still trying to learn Scala's Cake Pattern. It seems to me that it gives you the advantage of centralizing your configuration of "Components" as well as the ability to provide default implementations for those components (which are of course overridable).

However, it's use of self-type traits to describe dependencies seems to mix area-of-concerns. The purpose of the Component (I think) is to abstract away the different implementations for that component. But the dependency-listing described in the Component is itself an implementation concern.

For instance, let's say I have a database full of widgets, a registry that allows me to look up specific kinds of widgets, and some sort of algorithm that uses the registry to process the widgets:

case class Widget(id: Int, name:String)

trait DatabaseComponent {
  def database: (Int => Widget) = new DefaultDatabase()

  class DefaultDatabase extends (Int => Widget) {
    // silly impl
    def apply(x: Int) = new Person(x, "Bob")
  }
}

trait RegistryComponent {
  this: DatabaseComponent =>  // registry depends on the database

  def registry: (List[Int] => List[Widget]) = new DefaultRegistry()

  class DefaultRegistry extends (List[Int] => List[Widget]) {
    def apply(xs: List[Int]) = xs.map(database(_))
  }
}

trait AlgorithmComponent {
  this: RegistryComponent =>  // algorithm depends on the registry

  def algorithm: (() => List[Widget]) = new DefaultAlgorithm()

  class DefaultAlgorithm extends (() => List[Widget]) {
    // look up employee id's somehow, then feed them
    // to the registry for lookup
    def apply: List[Widget] = registry(List(1,2,3))
  }
}

And now you can put it together in some central config:

object Main {
  def main(args: Array[String]) {
    val algorithm = new AlgorithmComponent() with RegistryComponent with DatabaseComponent

    val widgets = println("results: " + algorithm.processor().mkString(", "))
  }
}

If I want to change to a different database, I can inject it easily by changing my mixin:

val algorithm = new AlgorithmComponent() with RegistryComponent with SomeOtherDatabaseComponent


But...what if I want to mix in a different Registry component that doesn't use a database?

If I try to subclass the RegistryComponent with a different (non-Default) implementation, the RegistryComponent will insist that I include a DatabaseComponent dependency. And I have to use RegistryComponent, because that's what the top-level AlgorithmComponent requires.

Am I missing something? The moment I use a self-type in any of my Components, I'm declaring that all possible implementations must use those same dependencies.

Has anyone else run into this issue? What is the Cake-like way of solving it?

Thanks!

like image 418
shj Avatar asked Mar 08 '12 16:03

shj


People also ask

What is the cake pattern in Scala?

It’s called the Cake pattern, a sort of dependency injection mechanism that uses only idiomatic Scala constructs. The cake pattern represents the most important use of the self-type annotation in Scala. The pattern defines two different aspects of dependency management. The first is how to declare a dependency.

What is the Scala way of dependency injection?

Dependency injection, "the scala way" (among other possible usages, but this post focuses on this one) What is Dependency Injection? The reverse of look-ups / named dependencies, e.g. if class X needs a database connection and gets it using

What is a cake pattern in C++?

Cake pattern Cake pattern allows to build system as a layered components. To build a component it uses traits. Component consists with multiple layers. Because of the layered architecture of a component, it named as Cake-Pattern. Components are injecting (mixing) to other components via self typed annotation.


1 Answers

With the cake pattern, at least by the example I always go to, you should separate the component's interface definition from its default implementation. This cleanly separates the interface's dependencies from the implementation's dependencies.

trait RegistryComponent {
  // no dependencies
  def registry: (List[Int] => List[Widget])
}

trait DefaultRegistryComponent extends RegistryComponent {
  this: DatabaseComponent =>  // default registry depends on the database

  def registry: (List[Int] => List[Widget]) = new DefaultRegistry()

  class DefaultRegistry extends (List[Int] => List[Widget]) {
    def apply(xs: List[Int]) = xs.map(database(_))
  }
}
like image 74
leedm777 Avatar answered Sep 23 '22 08:09

leedm777