お久しぶりです。
Scala でDIっぽいことをやる方法と申しますと、
こういったアプローチがあるのですが、
これだと、inject用のTraitを山のように定義しないといけなくて、煩雑な気がしたので、自分で簡単なDIの仕組みを作ってみました。
こんな感じです↓
1 import scala.collection.immutable.Map
2
3 trait Key[+T]
4
5 class Component(entries:Map[Key[Any],()=>Any]) {
6
7 def define[T](key:Key[T], instance: => T) =
8 new Component(entries + (key -> (()=>instance)))
9
10 def apply[T](key:Key[T]):T =
11 instance(entries(key)).asInstanceOf[T]
12
13 private def instance[T](factory:()=>T):T = factory()
14 }
15
16 object Component {
17 def define[T](key:Key[T], instance: => T) =
18 new Component(Map.empty + (key -> (()=>instance)))
19 }
やってみたら、なんとたった19行で出来ちゃいました。
まぁ、エラー処理とかちゃんとやるともうちょい長くなりますけどね。。
あと、11行目とか、なぜこれでコンパイルエラーにならないのか自分でもわかってないです。
キャストしようとしても T がなにかわからないはずなのに。
まだまだ勉強がたりないですね。
使い方はこんなです↓
1 //コンポーネント
2 class A(inject:Component) {
3 val b = inject(B)
4
5 def sayHello {
6 b.sayHello
7 }
8 }
9
10 object A extends Key[A]
11
12 class B(inject:Component) {
13 val c = inject(C)
14
15 def sayHello {
16 c.sayHello
17 }
18
19 }
20
21 object B extends Key[B]
22
23 class C {
24 def sayHello {
25 println("Hello, I am C")
26 }
27 }
28
29 object C extends Key[C]
30
31 //コンポーネントをくっつける定義
32 val component:Component =
33 Component.define(A, new A(component))
34 .define(B, new B(component))
35 .define(C, new C)
36
37
38 //使ってみよう
39 val a = component(A)
40
41 a.sayHello
42
39行目で クラスA のインスタンスが生成されます。
A の sayHello が Bの sayHello を呼び出し、B のsayHelloが cのsayHelloを呼び出すので、
このコードを実行すると「Hello, I am C」と出力されます。
ポイントは、コンパニオンオブジェクトがコンポーネントのキーになってるとこですかね。
Componentクラスは、コンパニオンオブジェクトをキー、対応するクラスのインスタンスを生成する関数を値とするMap で、
他のコンポーネントに依存するコンポーネントは、このComponentクラスのインスタンスを保持して
自分を初期化するときに、Component クラスから必要なコンポーネントのインスタンスを取得しています。
Scala では、なるべくコンパイラに仕事をさせて、ランタイムエラーが発生しないようなコードを書くのが良いと
思っているのですが、この例では必要なコンポーネントが見つからなかったときにランタイムエラーが発生してしまいます。
というデメリットはあるのですが、これだけ単純な仕掛けでコードがぐっと簡潔になるので、
それを補うだけのメリットもあるのではないかと思います。