The Cake Pattern による DI
前に、Java の DI コンテナと Scala の組み合わせはどうにもいまいちと Twitter でつぶやいていたら、とある人から Programming Scala に DI のサンプルがあるよ!っておしえてもらいました。
以下の URL は Programming Scala の DI についての箇所です。*1
http://programming-scala.labs.oreilly.com/ch13.html#DependencyInjectionInScala
以下、私が理解した内容を忘れないようにメモっておきます。
読んでみると、DI コンテナのかわりに Cake パターンなる方法で、DI が対象とする問題を解決出来る模様です。*2
例として出ている Twitter クライアントのコンポーネントをクラス図にしてみました。
各コンポーネントの役割は以下のとおり。
- TwitterClientUIComponent - UI
- TwitterLocalCacheComponent - ローカルキャッシュ
- TwitterServiceComponent - Twitter サービスにアクセスする
- TwitterClientComponent - クライアント
これらのコンポーネントはすべて同じ構造をとっています。
- コンポーネントの振る舞いをあらわす、ネストしたトレイトやクラスを定義している
- ネストしたトレイトやクラスの型をもつ val を宣言している
この時点での各コンポーネント間の依存は以下のとおり。
- TwitterClientUI → Tweeter
- TwitterClient → TwitterClientUI, TwitterLocalCache, TwitterService
TwitterClientUI と TwitterClient 間で依存関係が循環しているような気がしないでもないですが、TwitterClient → TwitterClientUI は実行時に参照しているだけなので多分大丈夫。
ここまできたら、すべての 〜Component トレイトを継承したクラスを作成して、各コンポーネントの実装を設定すれば良いはずです。この 〜Component トレイトを継承したクラスが、DI コンテナでの設定ファイルに対応するのだと思います。
例では TextClient というクラスで、すべての 〜Component トレイトを継承して、各コンポーネントの実装を設定しています。
以下は、試しに例をコーディングして気になったことです。
- TextClient クラスでは実装を選択するだけにしたい。例のように各コンポーネントの実装自体をしていると、各コンポーネントに対する単体テストがやりづらいように思える。
- DI コンテナで設定ファイルにあたるクラスで大量のトレイトを継承する必要が出てくる。設定ファイルを分割するように、うまいことクラスを分割できないだろうか?
とりあえず、私の理解した Cake パターンの使い方は以下のとおり・・・かな。
- コンポーネントごとにトレイトを作成する
- trait FooComponent とか
- コンポーネントの振る舞いはネストしたトレイトor抽象クラスとして定義する
- trait FooComponent { trait Foo } とか
- 振る舞いのインスタンスを保持する抽象valを、コンポーネントのトレイトに宣言する
- trait FooComponent { val foo: Foo; trait Foo } とか
- 利用側コンポーネントは、利用するコンポーネントのトレイトを self-type annotation で指定する
- trait BarComponent { self: FooComponent =>; val bar: Bar; trait Bar }
- 必要なコンポーネントのトレイトを継承した、コンテナ代わりのクラスを作成し実装を設定する
- class Baz extends FooComponent with BarComponent { val foo = new Foo {...}; val bar = new Bar {...} }
- コンテナ代わりのクラスをインスタンス化し、コンポーネントを利用する
実際に使えば、もう少しいろいろ見えてくるかもしれませんが、まだなんともしっくりきません・・・(´・ω・`)
追記
興味のある方は、単純な例で Cake Pattern を試した Cake Pattern の簡単な例を書いておく も参考にどうぞ
*1:Programming Scala はネット上で読めたりします。ありがたい。http://programming-scala.labs.oreilly.com/
*2:"the Cake Pattern, which can replace or complement these other dependency injection mechanisms." なので、置き換える、または補完するってことですかね・・・