LoginSignup
23
17

More than 5 years have passed since last update.

KotlinでもMinimal Cake Pattern

Last updated at Posted at 2015-12-10

Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜 で紹介されている Minimal Cake Pattern を Kotlin でやってみました。

元記事と同じように UserRepositoryUserService で書きます。比較しやすいように、言葉もほぼ同じものを使っています。

まずは、元記事を読みましょう。

・・・読みましたか?読みましたね?

さて、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への移植を試してみたらすんなり出来てしまったので、実用的かどうかしばらくこのパターンを試してみようかと思っています。

23
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
17