テストを簡単にする DI を簡単に説明してみるテスト::Android風味

# はじめに
こんにちは。これは [Hamee Advent Calendar 2020](https://qiita.com/advent-calendar/2020/hamee) 6日目の記事です。

# これって何の話？
私の現在のお仕事は主に Android アプリの開発なのですが、その中では Dependency Injection (DI) を多用しています。
そのこと自体は今となってはそれほど珍しくないことだと思いますが、DI は少しとっつきにくい概念でもあるので、「DI って何がおいしいの？」とか「よくわからんけど DI が良いらしいから使うべ」という人もいるんじゃないかなと勝手に思ったりしています。
だって「Dependency Injection」でググっても概念的な解説が多くて、なかなか頭に入らないというか、分かったような分からないようなだったりしません？私だけかな。。

というわけでこの記事では、概念的な話からではなく、具体的な例から DI とは何なのかを紐解いてみようかなと思います。ついでに Android のいわゆるモダンな開発での活用方法みたいな話まで持っていこうと思います。

あ、具体的な例から解説していくと、どうしても網羅的な説明は難しくなってしまいます（DI は解説するなら○○も書くべきだ的な）。そこはどうかご理解いただきたいです。。。[^masakari]

[^masakari]: マサカリはいったん床に置きましょう！

# 例題1: 円柱の体積を計算する機能
「半径と高さから円柱の体積を計算する」アプリを作ることを考えてみたいと思います。

## 機能の分解
昨今のソフトウェア開発では複雑な機能をなるべく単純な機能に分解して開発していくのが一般的です。
今回の例題では

- **機能A** : 半径と高さから円柱の体積を計算する

という機能が求められていますが、この機能は少し複雑な気がしないでもないような気がしますので（異論は認めない！）[^complex]、以下の2つに機能に分解しましょう。

- **機能B** : 半径から円の面積を計算する
- **機能C** : 面積と高さから体積を計算する

コードにするとこんな感じでしょうか。

```kotlin
object CylinderVolumeCalculator {
    // 機能A: 半径と高さから円柱の体積を計算する
    fun calcCylinderVolume(radius: Double, height: Double): Double {
        return calcVolume(calcCircleArea(radius), height)
    }

    // 機能B: 半径から円の面積を計算する
    private fun calcCircleArea(radius: Double): Double {
        return radius * radius * Math.PI
    }

    // 機能C: 面積と高さから体積を計算する
    private fun calcVolume(area: Double, height: Double): Double {
        return area * height
    }
}
```

このコードは問題なく動くはずです。本体プログラムから `CylinderVolumeCalculator.calcCylinderVolume()` 静的メソッドを呼べば正しく動作するでしょう。ですがこういう書き方は、以下のような観点からあまりよろしくないと言われています。

- **問題1**: 分解された機能B、機能Cが非公開のためテストできない。
- **問題2**: 公開されている機能Aもテストしにくい。

[^complex]: 言うまでもないと思いますが、こんな簡単な処理をわざわざ分解するなんて、普通はやらないと思います。

## 問題1: 分解された機能ごとのテストができない
複雑さを嫌って機能を分解したのですから、単体テストも分解された単純な機能ごとに行うべきでしょう。それなのに機能Bと機能Cのメソッドが非公開（`private`フィールド）になっていてはテストのしようがありません。まずはこれらを公開メソッドにしてあげましょう。

```kotlin
object CylinderVolumeCalculator {
    // 機能A: 半径と高さから円柱の体積を計算する
    fun calcCylinderVolume(radius: Double, height: Double): Double {
        return calcVolume(calcCircleArea(radius), height)
    }

    // 機能B: 半径から円の面積を計算する
    fun calcCircleArea(radius: Double): Double {
        return radius * radius * Math.PI
    }

    // 機能C: 面積と高さから体積を計算する
    fun calcVolume(area: Double, height: Double): Double {
        return area * height
    }
}
```

これで機能B, Cの単体テストは問題なく書けるでしょう。

## 問題2: 公開されている機能Aもテストしにくい
`calcCylinderVolume()` メソッドは元から公開されていますからテストを書くことはできます。が、ちょっと厄介な問題を抱えています。
このメソッドの機能は、本来的には「入力された半径と高さから円柱の体積を計算して返却する」ですが、機能を分解した結果、単体としての機能（スペック）は以下のように変わっています。

- 機能Bに半径を渡して円の面積を受け取る。
- 機能Cに面積と高和を渡して体積を受け取る。
- 受け取った体積を返却する。

どういうことかというと、仮に機能Bの実装にミスがあって円の面積が間違っていたとしてもそれは機能B側の問題であって機能Aの問題ではありません。同様に機能Cの実装にミスがあって体積が間違っていたとしてもそれは機能Cの問題であって機能Aの問題ではありません。機能Aの役割はあくまで、受け取ったデータを機能Bや機能Cに渡したり受け取ったり返却したりすることだけです。言い換えれば、機能Aの単体テストは、受け取ったデータを正しく機能Bや機能Cに渡したり返却したりできているかだけを確認できれば良いことになります（もちろん機能Bと機能Cは十分にテストされていることが前提となります）。

このような場合、機能B、機能Cの想定される正しい挙動を模したモックを用意して機能Aのテストを行うのが一般的です。しかし `calcCylinderVolume()` の実装は、`calcCircleArea()` メソッドや `calcVolume()` メソッドを直接呼んでしまっているため、モックの使いようがありません。どうしたものでしょうか？

## モックを使えるようにする
機能B、機能Cのモックを作成し、また使えるようにするには、一般に各機能のインタフェースを用意します。[^higher_order]

```kotlin
// 機能Bのインタフェース
interface CalcCircleAreaFunction {
    companion object {
        fun newInstance(): CalcCircleAreaFunction = CalcCircleAreaFunctionImpl()
    }
    
    fun calcCircleArea(radius: Double): Double
}

// 機能Bの実装
class CalcCircleAreaFunctionImpl : CalcCircleAreaFunction {
    override fun calcCircleArea(radius: Double): Double {
        return radius * radius * Math.PI
    }
}

// 機能Cのインタフェース
interface CalcVolumeFunction {
    companion object {
        fun newInstance(): CalcVolumeFunction = CalcVolumeFunctionImpl()
    }

    fun calcVolume(area: Double, height: Double): Double
}

// 機能Cの実装
class CalcVolumeFunctionImpl : CalcVolumeFunction {
    override fun calcVolume(area: Double, height: Double): Double {
        return area * height
    }
}
```

こんな感じですね。ちなみに各インタフェースにファクトリメソッド（`newInstance()`）を用意しているのは、これらの機能を利用する側が実装クラス（`CalcCircleAreaFunctionImpl`と`CalcVolumeFunctionImpl`）を意識しなくて済むようにするためです。こうすれば、機能Aはとりあえず以下のように書き換えることができます。

```kotlin
    // 機能Aの実装（円柱の体積を計算する）
    fun calcCylinderVolume(
        radius: Double,
        height: Double,
        calcCircleAreaFunction: CalcCircleAreaFunction = CalcCircleAreaFunction.newInstance(),
        calcVolumeFunction: CalcVolumeFunction = CalcVolumeFunction.newInstance()
    ): Double {
        return calcVolumeFunction.calcVolume(calcCircleAreaFunction.calcCircleArea(radius), height)
    }
```

機能Aのメソッド `calcCylinderVolume()` に引数を追加して機能B、機能Cの関数オブジェクトを受け取れるように拡張しています。デフォルトで各機能の通常の実装が渡されるようになっているため `calcCylinderVolume()` メソッドの使い方に変化はありませんが、単体テストではこれらの引数に各機能のモックオブジェクトを渡すことで `calcCylinderVolume()` メソッドをテストできます。たとえば以下のように書けるでしょう。[^test][^test2]

```kotlin
class CylinderVolumeCalculatorTest {
    @Test
    fun calcCylinderVolume_success() {
        // テストで使うパラメータ
        val requestRadius = 2.0
        val requestHeight = 3.0

        // 機能Bのモックが返す値
        val expectArea = requestRadius * requestRadius * 3.14

        // 機能Cのモックが返す値
        val expectVolume = expectArea * requestHeight

        // 機能Bのモック
        val calcCircleAreaFunctionMock = object : CalcCircleAreaFunction {
            override fun calcCircleArea(radius: Double): Double {
                // 機能Aは機能Bに対して requestRadius の値を渡していなければならない
                assertThat(radius, `is`(requestRadius))
                return expectArea
            }
        }

        // 機能Cのモック
        val calcVolumeFunctionMock = object : CalcVolumeFunction {
            override fun calcVolume(area: Double, height: Double): Double {
                // 機能Aは機能Cに対して requestHeight の値を渡していなければならない
                assertThat(height, `is`(requestHeight))
                // 同様に機能Aは機能Cに、機能Bが返した expectArea の値を渡していなければならない
                assertThat(area, `is`(expectArea))
                return expectVolume
            }
        }

        // テスト対象のメソッドを実行する
        // その際、機能B、機能Cのモックを渡す
        val volume = CylinderVolumeCalculator.calcCylinderVolume(
            requestRadius,
            requestHeight,
            calcCircleAreaFunctionMock,
            calcVolumeFunctionMock
        )
        // 機能Aは機能Cが返した expectVolume の値を返却しなければならない
        assertThat(volume, `is`(expectVolume))
    }
}
```

[^higher_order]: Kotlin ならインタフェースじゃなく高階関数を使えば良いんじゃない？というツッコミが飛んできそうですが、はい、この例ではそうですよね。ですがモック化する対象のオブジェクトは通常、複数のメソッドを持っていますので、インタフェースを使うということでご理解いただきたく…（例が悪いという説）

[^test]: この記事ではモックをすべて自前で実装していますが [Mockito](https://github.com/mockito/mockito) や [MockK](https://github.com/mockk/mockk) を使えばもっと簡単に実装できます。

[^test2]: このテストコード、「円柱の体積の計算なんてめっちゃ単純な処理のためにこんな面倒なテスト書かなきゃいけないんかい！」というツッコミをもらえそうですね。もちろんこれは「DI とは何か」を解説するために単純な処理を題材にしたからなのですが、ある意味、「必要以上に機能を分解してもデメリットが多い」ことを示す例にもなっていますね。

## 依存する機能の「注入」とは

ここでもう一度、機能A（`calcCylinderVolume` メソッド）の実装を見てみましょう。

```kotlin
    // 機能Aの実装（円柱の体積を計算する）
    fun calcCylinderVolume(
        radius: Double,
        height: Double,
        calcCircleAreaFunction: CalcCircleAreaFunction = CalcCircleAreaFunction.newInstance(),
        calcVolumeFunction: CalcVolumeFunction = CalcVolumeFunction.newInstance()
    ): Double {
        return calcVolumeFunction.calcVolume(calcCircleAreaFunction.calcCircleArea(radius), height)
    }
```

機能Aは機能B（`calcCircleAreaFunction`）と機能C（`calcVolumeFunction`）を、引数として受け取り、そしてそれらを利用して、自分自身の機能を実現しています。機能Aは機能Bと機能Cに依存していて、それら「依存する機能」が外部から送り込まれているようにも見えます。そのためこのような設計パターンは Dependency Injection (DI) と呼ばれています（個人的にはこの名称はあまり好きではないのですが…）。

ただし、一般に DI と言った場合、上記の実装のように依存する機能を直接メソッドに渡すことはあまりありません。というのも、機能Aを使う側（`calcCylinderVolume` メソッドを呼び出す側）から見ると、本来なら半径と高さの２つのパラメータさえ渡せば良いはずのメソッドなのに、なんだかよく分からない引数を余計に渡さないといけないように見えてしまうからです。それに第3引数と第4引数を渡さなかった場合、つまりデフォルト値が利用された場合、`CalcCircleAreaFunctionImpl`クラスと`CalcVolumeFunctionImpl`クラスがインスタンス化されてしまうので、機能Aが連続して呼ばれるようなシーンではパフォーマンス面でも問題になるかも知れません。

そのため依存する機能はメソッドに直接渡すのではなく、そのメソッドが属するクラスに渡すのが一般的だと思います。

```kotlin
class CylinderVolumeCalculator(
    private val calcCircleAreaFunction: CalcCircleAreaFunction = CalcCircleAreaFunction.newInstance(),
    private val calcVolumeFunction: CalcVolumeFunction = CalcVolumeFunction.newInstance()
) {
    // 機能A: 半径と高さから円柱の体積を計算する
    fun calcCylinderVolume(radius: Double, height: Double): Double {
        return calcVolumeFunction.calcVolume(calcCircleAreaFunction.calcCircleArea(radius), height)
    }
}
```

この例ではクラスのコンストラクタで依存する機能を渡すようにしていますが、クラスがインスタンス化されたあとに渡すようにすることもあります。
ちなみにこうした場合、テストコードは以下のように書き換える必要があります（変更箇所のみ抜粋）。

```kotlin
        // テスト対象のメソッドを実行する
        // その際、機能B、機能Cのモックを渡す
        val volume = CylinderVolumeCalculator(calcCircleAreaFunctionMock, calcVolumeFunctionMock)
            .calcCylinderVolume(requestRadius, requestHeight)
```

# 例題2: BGM の再生と停止をする機能
次に、もう少し Android アプリ開発っぽい例を考えてみましょう。
アプリ内で BGM を再生したり停止したりする機能を作成してみようと思います。

## ざっと実装
Android で音楽を再生するには`MediaPlayer`クラスを使うのが一般的だと思います。そこで`MediaPlayer`クラスを簡単に使うためのラッパークラスを書いてみましょう。

```kotlin
interface BgmPlayer {
    companion object {
        fun newInstance(context: Context): BgmPlayer =
            BgmPlayerImpl(context)
    }

    // 再生
    fun play()
    // 停止
    fun stop()
}

class BgmPlayerImpl(context: Context) : BgmPlayer {
    private val mediaPlayer = MediaPlayer.create(context, R.raw.battle_bgm).apply {
        isLooping = true
    }

    // 再生
    override fun play() {
        mediaPlayer.start()
    }

    // 停止
    override fun stop() {
        mediaPlayer.stop()
    }
}
```

`BgmPlayerImpl`クラスは問題なく動作するはずです。ですがこの実装にはいくつか問題があります。一つずつ見ていきましょう。

## 問題1: `BgmPlayerImpl`クラス自体をテストしにくい
これは例題1でも問題になった「依存する機能を内包してしまっている」ことに起因します。今回の例題では`BgmPlayerImpl`クラスが内部的に`MediaPlayer`クラスに依存しており、かつ private メンバとして保持しているためモックに置き換えることができません。そこで例題1でやったのと同じように、`MediaPlayer`をコンストラクタで渡せるようにしてみましょう。

```kotlin
interface BgmPlayer {
    companion object {
        fun newInstance(mediaPlayer: MediaPlayer): BgmPlayer =
            BgmPlayerImpl(mediaPlayer)

        fun newInstance(context: Context): BgmPlayer =
            BgmPlayerImpl(
                MediaPlayer.create(context, R.raw.battle_bgm).apply {
                    isLooping = true
                }
            )
    }

    // 再生
    fun play()
    // 停止
    fun stop()
}

class BgmPlayerImpl(
    private val mediaPlayer: MediaPlayer
) : BgmPlayer {
    // 再生
    override fun play() {
        mediaPlayer.start()
    }

    // 停止
    override fun stop() {
        mediaPlayer.stop()
    }
}
```

`BgmPlayerImpl`クラスのコンストラクタは`MediaPlayer`を受け取るようにして、「`Context`から`MediaPlayer`を生成する」という処理はファクトリメソッド側で行うよう修正しました。[^secondary_constructor]
これで`BgmPlayerImpl`クラスに`MediaPlayer`を注入できるようになりましたね。
こうすれば`BgmPlayerImpl`クラスの単体テストは、たとえば以下のように書けそうです。

```kotlin
class BgmPlayerTest {
    @Test
    fun start_and_stop_success() {
        var isStartCalled = false
        var isStopCalled = false

        // MediaPlayer のモックを作る
        val mediaPlayerMock = object : MediaPlayer() {
            override fun start() {
                isStartCalled = true
            }

            override fun stop() {
                isStopCalled = true
            }
        }

        // テスト対象のクラスをインスタンス化する
        // ただし MediaPlayer にはモックを使う
        val bgmPlayer = BgmPlayerImpl(mediaPlayerMock)

        // play メソッドのテスト: MediaPlayer の start メソッドが呼ばれること
        bgmPlayer.play()
        assertTrue(isStartCalled)

        // stop メソッドのテスト: MediaPlayer の stop メソッドが呼ばれること
        bgmPlayer.stop()
        assertTrue(isStopCalled)
    }
}
```

[^secondary_constructor]: 「ContextからMediaPlayerを生成する」処理を`BgmPlayerImpl`のセカンダリコンストラクタとして実装する方法もありますが、「MediaPlayerの生成処理はテスト対象外である」ことが明示的になるようファクトリメソッド側に書いてしまうのが個人的には好みです。

## 問題2: 複数個所から呼ばれる場合どうするか
BGM の再生/停止は、アプリの色々な箇所から呼ばれる可能性があります。画面が複数あるアプリでは、たとえば画面Aで BGM の再生を開始し、画面Bに遷移してから BGM を停止するということも十分考えられます。そのような場合、たとえば以下のように実装しても良いものでしょうか？

```kotlin:FragmentA.kt
    private val bgmPlayer: BgmPlayer by lazy { BgmPlayer.newInstance(requireContext()) }

    // 画面Aの Fragment で BGM 再生開始
    private fun playBgm() {
        bgmPlayer.play()
    }
```

```kotlin:FragmentB.kt
    private val bgmPlayer: BgmPlayer by lazy { BgmPlayer.newInstance(requireContext()) }

    // 画面Bの Fragment で BGM 停止
    private fun stopBgm() {
        bgmPlayer.stop()
    }
```

現在の Android アプリの開発では、多くの場合、画面ごとに異なる Fragment を用意してその Fragment ごとに動作を制御するのが一般的です。上記の例でもそれに倣って、画面Aの Fragment (`FragmentA`) と画面Bの Fragment (`FragmentB`) にそれぞれ再生と停止のコードを入れてみました。ですがこれでは正しく動作しません。なぜならそれぞれの箇所で`BgmPlayerImpl`クラスの新しいインスタンスを生成してしまっているためです。正常に動作させるには`FragmentA`と`FragmentB`で同じインスタンスを共有する必要があります。異なる Fragment 間でオブジェクトを共有するにはどうすれば良いでしょうか？

その一つの方法として、共有したいオブジェクトを`Application`クラスの派生クラスに保持させることが考えられます[^application_class]。たとえば以下のようにします。

```kotlin:DIContainer.kt
// アプリ内で共有するオブジェクトのコンテナ
class DIContainer(context: Context) {
    val bgmPlayer: BgmPlayer by lazy { BgmPlayer.newInstance(context) }
}
```
```kotlin:MainApplication.kt
class MainApplication : Application() {
    lateinit var diContainer: DIContainer
        private set

    override fun onCreate() {
        super.onCreate()
        diContainer = DIContainer(applicationContext)
    }
}
```

こうしておけば、各 Fragment で同一インスタンスの `BgmPlayer` を以下のように取得できます。

```kotlin:FragmentA.kt
class FragmentA : Fragment() {
    private lateinit var diContainer: DIContainer
    private lateinit var bgmPlayer: BgmPlayer

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        diContainer = (requireActivity().application as MainApplication).diContainer
        bgmPlayer = diContainer.bgmPlayer
    }
}
```

これが DI コンテナを使った DI となります。これまで見てきた、コンストラクタを介しての「機能の注入」とは少し勝手が違いますが、「特定の機能を外部から受け取っている」という意味で同じ意味合いを持ちます。

[^application_class]: アプリ全体で共有するオブジェクトを Application クラスに保持させる理由は[以前書いた記事](https://qiita.com/ishihatta/items/831b063ef14b41129df9#application%E3%81%AE%E6%B4%BE%E7%94%9F%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E4%BB%8B%E3%81%97%E3%81%A6%E3%82%84%E3%82%8A%E5%8F%96%E3%82%8A%E3%81%99%E3%82%8B%E3%81%A3%E3%81%A6%E3%81%A9%E3%81%86%E3%81%84%E3%81%86%E3%81%93%E3%81%A8)をご参照いただければ！

# Hilt の導入
ここまで DI の基本的な考え方を書いてきました。単体テストを書く上で多くのメリットがある手法であることは確かなようですね。
その一方で DI を導入するには少し面倒くさいコードを書かなければならないというデメリットがあることも分かったと思います。例題2で見た`DIContainer`の実装と、それを使えるようにするための`Application`クラスや`Fragment`へのコードの追加がそれです。

ですがこれらの面倒くさいコードは、便利な DI コンテナライブラリを導入することで多くの部分を省略できるようになります。ここでは Hilt というライブラリを導入して例題2のコードを書き換えてみます。

なお、この記事では Hilt のさわり程度しか解説しません。より詳しく知りたい方は[こちら](https://developer.android.com/training/dependency-injection/hilt-android?hl=ja)等を参考にしてみてください。

## Hilt の初期設定
まずは開発環境に Hilt を組み込みます。

```gradle:build.gradle
buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}
```
```gradle:app/build.gradle
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
```

## `Application`クラスにアノテーションを付ける
次に`Application`クラス（の派生クラス）に`@HiltAndroidApp`のアノテーションを付けます。
また Hilt の導入によって自前の DI コンテナは不要になるため`DIContainer`のインスタンス化やそれを保持するためのインスタンス変数は不要になります。つまり`MainApplication`クラスの定義は以下だけで十分となります。

```kotlin:MainApplication.kt
@HiltAndroidApp
class MainApplication : Application()
```

## `Activity`や`Fragment`にもアノテーションを付ける
さらに`Activity`や`Fragment`（の派生クラス）に`@AndroidEntryPoint`のアノテーションを付けます。

```kotlin:MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    // 省略
}
```
```kotlin:FragmentA.kt
@AndroidEntryPoint
class FragmentA : Fragment() {
    // 省略
}
```
```kotlin:FragmentB.kt
@AndroidEntryPoint
class FragmentB : Fragment() {
    // 省略
}
```

## Hilt モジュールを作る
Hilt を使うには DI して使いたいクラス、つまり例題2でいう`BgmPlayer`のインスタンス化の方法を Hilt に知らせる必要があります。そのためには Hilt モジュールと呼ばれる object を定義する必要があります。この object は名前は何でも構いませんが`@Module`, `@InstallIn`のアノテーションを付ける必要があります。また`@Provides`アノテーションを付けたメソッドを定義する必要もあります。

```kotlin:BgmPlayerModule.kt
@Module
@InstallIn(ApplicationComponent::class)
object BgmPlayerModule {
    @Provides
    fun provideBgmPlayer(
        @ApplicationContext context: Context
    ): BgmPlayer = BgmPlayer.newInstance(context)
}
```

これで`BgmPlayer`の機能が要求されたときの`BgmPlayer`のインスタンス化の方法を Hilt に教えることができました。

## `BgmPlayerImpl`クラスをシングルトンにする
上記の設定で`BgmPlayer`のインスタンス化の方法を規定できましたが、このままだと`BgmPlayer`を注入するたびに新しい`BgmPlayerImpl`インスタンスが生成されてしまいます。例題2でも見たように、このクラスは複数個所から同じインスタンスを使われる想定になっています。つまりシングルトンである必要があります。
Hilt ではシングルトンにしたいクラスに`@Singleton`アノテーションを付けるだけでそれを実現できます。

```kotlin
@Singleton
class BgmPlayerImpl(
    private val mediaPlayer: MediaPlayer
) : BgmPlayer {
    // 省略
}
```

これでお膳立ては整いました。

## `BgmPlayer`の注入
以上で Hilt を使って`BgmPlayer`を DI できるようになりました。
たとえば例題2で見たのと同じように Fragment で`BgmPlayer`を使いたい場合は以下のように注入できます。

```kotlin:FragmentA.kt
@AndroidEntryPoint
class FragmentA : Fragment() {
    // BgmPlayerを注入する
    @Inject
    lateinit var bgmPlayer: BgmPlayer

    // BGMを再生する
    fun playBgm() {
        bgmPlayer.play()
    }
}
```

インスタンス変数`bgmPlayer`は初期化されていないように見えますが、`@Inject`アノテーションを付けることで Hilt が自動的に初期化してくれます。その際 Hilt モジュール（上で定義した`BgmPlayerModule` object）が参照され、適切に`BgmPlayerImpl`クラスがインスタンス化されます。あるいは、すでに`BgmPlayerImpl`のインスタンスが存在する場合はそれが利用されます（`@Singleton`アノテーションがあるため）。

例題2では DI の仕組みを自前で実装しましたが Hilt を使えば「お作法」に従って書いていくだけで済みます。自分でごちゃごちゃ考える必要がなくなりますし、コードもすっきりして見通しが良くなります。例題2でやった自前実装とほぼ同じことを Hilt が自動的にやってくれているわけですね。

## おまけ：ViewModel への注入
MVVM で開発する場合は ViewModel に注入することも可能です。

```gradle:app/build.gradle
android {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

dependencies {
    implementation "androidx.fragment:fragment-ktx:1.2.5"
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
    implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
    kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
}
```
```kotlin:AViewModel.kt
class AViewModel @ViewModelInject constructor(
    // BgmPlayerを注入する
    private val bgmPlayer: BgmPlayer
) : ViewModel() {
    // BgmPlayerを使ってBGMを再生する
    fun playBgm() {
        bgmPlayer.play()
    }
}
```
```kotlin:FragmentA.kt
@AndroidEntryPoint
class FragmentA : Fragment() {
    private val viewModel: AViewModel by viewModels()

    // AViewModel#playBgm()メソッドを呼んでBGMを再生する
    fun playBgm() {
        viewModel.playBgm()
    }
}
```

# おわりに
初めて Dependency Injection という言葉を耳にしたとき皆さんはどう感じたでしょうか？「はぁ？なんだそりゃ？」と思った人が多いのではないでしょうか？私もそのうちの一人です。私は中毒患者じゃないぞ。
DI のとっつきにくさはその名称にも原因があるんじゃないかなと思っています。依存性の注入…本当になんだそりゃですよ。。。

なのでこの記事では DI という言葉に振り回されないよう、どんなところからこういう考え方が生まれたのかという「ことの発端」みたいなところから書いてみたつもりです。またその話を「ふーん、概念はなんとなく分かった」で終わらせず、最新のモダンな開発で活かせるよう Hilt の使い方まで引っ張りました。執筆時間をあまり取れなかったのでだいぶ端折ってしまいましたが、参考にしてくれる人が少しでもいてくれたら嬉しいです。