「ジャバの異常な愛情 またはSpringはいかにしてモダンであることを止めて時代遅れになったのか」で言及されていたServiceLocatorをKotlinで作ってみました。
機能
機能 - 実装済
- Locatorからのインスタンスの取り出し
- Singleton/Prototypeの使い分け
- Mockへの入れ替え
これだけやって2クラスで60行も行かないんだからもうJavaには戻れない・・
機能 - TODO
- Enhancerによるカスタマイズを、利用側がInterceptorを書くだけで実装できるようにする
- Locator自体のインスタンスをカスタマイズに応じて複数扱えるようにする
機能に対する要望等あれば可能な限り対応します。
解説 - TODO
テスト
サンプルテスト 実装済み
こんな感じでターゲットクラスAとモッククラスMockedAを作っておきます。
package com.lavans.kasl
open class A {
open fun say() = "A: Hello!"
}
class MockedA : A() {
override fun say() = "MockedA: Yo!"
}
kotlinだとoverrideするメソッドはopenにしておかなきゃいけなくて、テストのためにインターフェースを変えるのは良くなさそう。拡張メソッドでどうにかならないかな。
1) 設定一切なしでインスタンス取得
@Test
fun `A says Hello!`() {
val a = Locator.get<A>(A::class)
println(a.say())
assertEquals("A: Hello!", a.say())
}
ちゃんとA: Hello!
が出力される。
2) AをMockedAに差し替え設定をしてから取得してみる
@Test
fun `MockedA says Yo!`() {
Locator.put(A::class, MockedA::class)
val a = Locator.get<A>(A::class)
println(a.say())
assertTrue(a.say().contains("Yo"))
}
たしかに振る舞いが変わった。
3) デフォルトのsingletonでは何回getしても同じインスタンスが取れること
@Test
fun `Singleton returns same instance`(){
val a = Locator.get<A>(A::class)
val b = Locator.get<A>(A::class)
val c = Locator.get<A>(A::class)
val d = Locator.get<A>(A::class)
println("$a\n$b\n$c\n$d")
assertEquals(a, b)
assertEquals(a, c)
assertEquals(a, d)
}
ちゃんと同じobject idで帰ってきますね
com.lavans.kasl.A@41675a1f
com.lavans.kasl.A@41675a1f
com.lavans.kasl.A@41675a1f
com.lavans.kasl.A@41675a1f
4) プロトタイプ指定すると別々のインスタンスになる
@Test
fun `Prototype returns different instance`(){
Locator.put(A::class, type = KClassInfo.Type.Prototype)
val a = Locator.get<A>(A::class)
val b = Locator.get<A>(A::class)
val c = Locator.get<A>(A::class)
val d = Locator.get<A>(A::class)
println("$a\n$b\n$c\n$d")
assertNotEquals(a, b)
assertNotEquals(a, c)
assertNotEquals(a, d)
}
なるほどー、全部違うIDだ。
com.lavans.kasl.A@309bde2d
com.lavans.kasl.A@42eda0a8
com.lavans.kasl.A@400f3528
com.lavans.kasl.A@522b5d4c
サンプル TODO
- AOPでloggerを差し込んでmethodの開始と終了でlogを吐くやつやりたい。そうするとloggerとcglibへの依存が増えるな、増やしたくないな、別プロジェクトでもいいけど