社内の勉強会で、matzがruby20行でDIコンテナを書いた話が話題になり、kotlinで実装してみました。
現状の実装だとすべてSingletonとなってしまいますが、シンプルさを優先ということで。
ソースをyamamotoj/DISampleにおいておきます。
DIコンテナ本体
DIContainer.kt
class DIContainer {
private class Item<T : Any>(val resolver: (DIContainer) -> T) {
private var instance: T? = null
fun resove(container: DIContainer) =
instance ?: resolver(container).apply { instance = this }
}
private val items = mutableMapOf<String, Item<out Any>>()
private fun <T> createKey(cls: Class<T>, name: String?): String = cls.name + name
@Suppress("UNCHECKED_CAST")
fun <T : Any> resolve(cls: Class<T>, name: String? = null): T =
synchronized(items) { (items[createKey(cls, name)] as Item<T>) }.resove(this)
fun <T : Any> register(cls: Class<T>, name: String? = null, resolver: (DIContainer) -> T) =
synchronized(items) { items.put(createKey(cls, name), Item(resolver)); Unit }
}
依存関係の定義
今回injectするクラス群はこちら
class A()
class B(val a: A)
class C()
class D(val b: B, val c: C)
Application classで依存関係を定義します。
DISampleApplication.kt
class DISampleApplication : Application() {
val container by lazy {
// dependency definition
DIContainer().apply {
register(A::class.java) { A() }
register(B::class.java) { B(resolve(A::class.java)) }
register(C::class.java) { C() }
register(D::class.java) { D(resolve(B::class.java), resolve(C::class.java)) }
}
}
}
Activityへの注入
by lazy
が便利です。
MainActivity.kt
class MainActivity : AppCompatActivity() {
// inject
val d by lazy { (application as DISampleApplication).container.resolve(D::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("log", d.toString())
}
}