Cake Pattern の簡単な例を書いておく
Cake Pattern の説明は Programming Scala よりも、http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di/ の方が分かりやすかったです。
一応、自分なりに理解できたつもりでいるので、とりあえず簡単な例を書いておきます。
出現するコンポーネントは Foo、Bar の二つ。Bar が Foo に依存している例です。
package sample01 trait FooComponent { val foo: Foo class Foo { def output(text: String) = println(text) } } trait BarComponent { self: FooComponent => val bar: Bar class Bar { def process(name: String) = foo.output("Hello %s world!".format(name)) } } object ComponentRegistry extends FooComponent with BarComponent { val foo = new Foo val bar = new Bar } object Main { def main(args: Array[String]) = ComponentRegistry.bar.process("Scala") }
実行すると "Hello Scala world!" と出力されます。
この例の場合、コンポーネント提供側と利用側とが同じtraitを使用しています。このため、いまいち理解しづらいかもなので、trait を分割してみます。*1
package sample02 // コンポーネント利用者に公開 trait Foo { def output(text: String): Unit } trait FooInstance { val foo: Foo } trait Bar { def process(name: String): Unit } trait BarInstance { val bar: Bar } // コンポーネントの実装 trait FooComponent { class FooImpl extends Foo { def output(text: String) = println(text) } } trait BarComponent { self: FooInstance => class BarImpl extends Bar { def process(name: String) = foo.output("Hello %s world!".format(name)) } } // コンポーネントリポジトリ object ComponentRegistry extends FooInstance with FooComponent with BarInstance with BarComponent { val foo = new FooImpl val bar = new BarImpl } object Main { def main(args: Array[String]) = ComponentRegistry.bar.process("Scala") }
この例では、Bar コンポーネント(の実装クラスである BarImpl)は FooInstance トレイトのフィールド val foo: Foo を参照して、Foo コンポーネントを利用しています。このとき、Bar コンポーネントは Foo コンポーネントの実装に依存することなく、また foo には有効な値が設定されていることを前提として利用できています。これは、DI コンテナ利用時に、依存コンポーネントをフィールドとして参照するのと似ているかと思います。
また、DI コンテナ利用時は、コンテナが依存コンポーネントをフィールドに設定してくれますが、Cake Pattern の場合はすべてのコンポーネントを保持する ComponentRegistry で、各フィールドにコンポーネントを設定します。ちなみに、フィールドは val として宣言されているため、設定漏れがある場合はコンパイルエラーとなります。実行せずとも設定漏れに気付けるというのは嬉しい点です。
Cake Pattern を利用した場合、DI コンテナが提供する機能のいくつかを言語の機能で実現することが出来ます。ただ、AOP であったりスコープの制御(リクエストのたびに生成等)であったりは DI コンテナの方が扱いやすいのかなと思います。うまく DI コンテナと Cake Pattern を併用できるとより良いのかも知れません。
追記(2010/01/19)
インタフェースと実装を分離するのは、http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di/ の一番下に書いてあったです。よく読んでいませんでした(汗
*1:複数の実装があるわけでもないので、Cake Pattern を実際に使用する時には trait を分割する必要は無いと思います。