subcut
https://github.com/dickwall/subcut
ドキュメント
トラブル
super constructor cannnot be passed a self reference unless parameter is declared by-name
というコンパイルエラーが発生する場合がある。Getting Startedを多少アレンジして、以下のようなコードを記述した場合。
trait WebSearch { } class GoogleSearchService()(implicit val bindingModule: BindingModule) extends WebSearch with Injectable {} .... object ProjectConfiguration extends NewBindingModule({ module => module.bind [WebSearch] toLazyInstance { new GoogleSearchService()(ProjectConfiguration) } })
おそらく、以前のバージョンのScalaでは正常だったのだろうが、最新2.9.1ではエラーになってしまう。 これはhttps://issues.scala-lang.org/browse/SI-5000でバグとして報告されている。 ごく簡単にこれを発生させるコードは以下。
class Bar(fn: => Unit) class Foo(val bar: Bar) object Sample extends Bar(new Foo(Sample))
とりあえず、以下のようにして回避することはできる。
object ProjectConfiguration extends NewBindingModule({ module => module.bind [WebSearch] toLazyInstance { new GoogleSearchService()(getProjectConfiguration) } }) def getProjectConfiguration = ProjectConfiguration
ソース
バイナリ非互換性
scalaのバイナリ非互換性のため、コンパイル済jarとしては2.8.0用、2.8.1用、2.9.0用が用意されている。
http://scala-tools.org/repo-releases/org/scala-tools/
TIPS
Injectableトレイトを継承したくないときのインジェクタ
通常は
class Sample()(implicit val bindingModule: BindingModule) extends Injectable { val something = inject[Something] }
などとするが、これではInjectableのメソッドはすべて公開されてしまう。 bindingModuleが入手できるのであれば、プライベートなインジェクタを以下のようにして作成してもよい
class Sample()(implicit val bindingModule: BindingModule) { val injector = new Injectable { val bindingModule = Sample.this.bindingModule } val something = injector.inject[Something] }
評価
- このフレームワークはDIというよりもむしろサービスロケータに近い。ただ、DIはもはや無用の長物と考えているのでそれでよい。
- bindingModuleの受け渡しにimplicitパラメータを用いているところは非常に評価できる。オブジェクトの取得や生成には必ずこれが必要になるものの、常に明示的に受け渡すのは面倒かと思われる。
- しかし、その一方でシステムを構成するすべてのクラスがこのカタチになっていなければならなくなる。一つでもそうでないものがあると、チェインが断ち切られてしまう。もちろん他のDIフレームワークも同じ欠点を持つが。
- デフォルトを指定できるのは良いし、そこで引数指定ができるのもよいが、モックの方は引数が指定できない。つまり、そのままではcontextual injectionはできない。また、この違いがあると、テスト時と本番時の動作の違いが生まれてしまい、うっかりすればテストにならない可能性もある。