Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜 で紹介されている Minimal Cake Pattern を Kotlin でやってみました。
元記事と同じように UserRepository
と UserService
で書きます。比較しやすいように、言葉もほぼ同じものを使っています。
まずは、元記事を読みましょう。
・・・読みましたか?読みましたね?
さて、Kotlin版を書いていきます。
インターフェース部分は以下のようになります。
interface UsesUserRepository {
val userRepository: UserRepository
}
interface UserRepository {
// メソッドの定義を書く
}
こうみると、Kotlin は Scala とかなり似ていますね。
UserRepository
と、それの対となるUsesUserRepository
を作りました。
UsesUserRepository
は、UserRepository
の依存を表すただひとつのプロパティ(Scalaと違い、Kotlinのvalはフィールドではない)を持ちます。
実装部分は以下のようになります。
interface MixInUserRepository : UsesUserRepository {
override val userRepository: UserRepository
get() = UserRepositoryImpl
}
object UserRepositoryImpl : UserRepository {
// 実装を書く
}
UserRepositoryImpl
は(DI用語の)サービスです。
そして、そのサービスと対になるMixInUserRepository
を作りました。
このMixInUserRepository
は、UserRepository
の実装を提供するただ一つのプロパティを実装したモジュールです。
(追記)
val
でScalaの変数のように書いてますが、実際にはプロパティのGetterをオーバーライドで定義しているだけです。そのため呼び出しごとにGetterメソッドが実行されてしまいます。
現在はinterfaceでプロパティの初期化やDelegated Propertyが定義できないためこのような実装になります。今後Kotlinで上記のような定義ができるようになるのであれば、Scalaと同じ動きになるかと思います。
(追記おわり)
利用する部分は以下のようになります。
interface UserService : UsesUserRepository {
// 実装を書く
companion object : UserService, MixInUserRepository
}
UserService
インターフェースは(DI用語の)クライアントです。そしてUsesUserRepository
を継承し、インターフェースのUserRepository
を使っていることを表明しています。
そして、UserService
インターフェースのコンパニオンオブジェクトは、UserService
インターフェースとMixInUserRepository
を継承し、サービスをクライアントにインジェクトしています。
具体例
元記事を補足した Minimal Cake Pattern のお作法のMixInは複数あってもいいという説明が具体例として分かりやすいと思ったので、そちらもKotlinで書いてみます。
import org.joda.time.DateTime
// 現在時刻を返す君
interface Clock {
fun now(): DateTime
}
// システムクロックの現在時刻を返す君
object SystemClock : Clock {
override fun now() = DateTime()
}
// あらかじめ設定した時刻を返し続ける君(ユニットテスト用)
class MockClock() : Clock {
private var currentTime = DateTime(0)
fun setCurrentTime(dateTime: DateTime) = {
currentTime = dateTime
}
override fun now() = currentTime
}
interface UsesClock {
val clock: Clock
}
interface MixInSystemClock : UsesClock {
override val clock: Clock
get() = SystemClock
}
interface MixInMockClock : UsesClock {
override val clock: Clock
get() = MockClock()
}
まとめ
KotlinのDIライブラリにはDelegated Propertyを使ったものが多いようですが、エラーの判明が実行してみないと分からないという、DIコンテナと同じ問題を持ちます。このMinimal Cake PatternだとScala版と同様にコンパイル時に解決するため、その点は解決しているといえます。
Kotlinへの移植を試してみたらすんなり出来てしまったので、実用的かどうかしばらくこのパターンを試してみようかと思っています。