DI (Dependency Injection) の基本

**DI (Dependency Injection)** とは何なのか？
そして、なぜ便利なのかを簡単に説明しようと思います。

## 他のクラスに依存していない場合
まずは何者にも依存していない簡単な`ViewModel`を作ってみます。

### ViewModel
```swift:swift
class GreetingViewModel {
    func greet() -> String {
        return "Hello"
    }
}
```
```kotlin:kotlin
class GreetingViewModel {
    fun greet(): String {
        return "Hello"
    }
}
```

`greet`メソッドが返す文字列を出力するコードはとりあえずこんな感じ。

```swift:swift
var viewModel = GreetingViewModel()

print(viewModel.greet())
```
```kotlin:kotlin
val viewModel = GreetingViewModel()

println(viewModel.greet())
```
こいつらを何かしらの方法(SwiftPlaygroundや[Try Kotlin](https://try.kotlinlang.org/#/Kotlin%20Koans/Conventions/In%20range/Task.kt)等)で動かしてみると...

### 結果
```
Hello
```
という感じで`Hello`と出力されます。
当たり前ですね。

## 他のクラスに依存している場合
`greet`メソッドが返す文字列を、他のクラスに依存するようにしてみます。

### 依存するクラス
```swift:swift
class GreetingRepository {
    func greet() -> String {
        return "Hello from Repository"
    }
}
```
```kotlin:kotlin
class GreetingRepository {
    fun greet(): String {
        return "Hello from Repository"
    }
}
```
`ViewModel`がこのクラスに依存するようにします。

### ViewModel
```swift:swift
class GreetingViewModel {

    private var greetingRepository = GreetingRepository()

    func greet() -> String {
        return self.greetingRepository.greet()
    }
}
```
```kotlin:kotlin
class GreetingViewModel {

    private val greetingRepository = GreetingRepository()

    fun greet(): String {
        return this.greetingRepository.greet()
    }
}
```

### 結果
```
Hello from Repository
```
`GreetingRepository`クラスから返ってきた文字列が表示されました。
問題なしですね。

## 依存するクラスを外から与える
では、この`GreetingViewModel`クラスの中で生成している`GreetingRepository`のインスタンスを
クラスの外で生成して`GreetingViewModel`クラスに与えてみます。

### ViewModel
`ViewModel`クラスのコンストラクタ引数で`GreetingRepository`のインスタンスを受け取るように修正します。

```swift:swift
class GreetingViewModel {
    private var greetingRepository: GreetingRepository

    init(greetingRepository: GreetingRepository) {
        self.greetingRepository = greetingRepository
    }

    func greet() -> String {
        return self.greetingRepository.greet()
    }
}
```

```kotlin:kotlin
class GreetingViewModel(private val greetingRepository: GreetingRepository) {
    fun greet(): String {
        return this.greetingRepository.greet()
    }
}
```
そうすると出力用コードはこんな感じになるかと思います。

```swift:swift
var viewModel = GreetingViewModel(greetingRepository: GreetingRepository())

print(viewModel.greet())
```
```kotlin:kotlin
val viewModel = GreetingViewModel(greetingRepository = GreetingRepository())

println(viewModel.greet())
```
これを実行してみると...
### 結果
```
Hello from Repository
```
先ほどと同じ出力になり、外から依存クラスのインスタンスを与えた場合でも同じ挙動になりました。

これを **Dependency Injection (依存性の注入)** と呼びます。

ただ、この状態ではクラス内でインスタンス化するのとほとんど変わりません。
なにか利点はあるのでしょうか？

たとえば、注入する`GreetingRepository`がまだ完成していないとしたらどうでしょう？

## インターフェイス（プロトコル）の活用

まだ完成していないクラスを使いたいとなったら、モックアップになるクラスを作って本物の代わりをさせます。

こんな感じでしょうか。

### モックアップ
```swift:swift
class GreetingRepositoryMock {
    func greet() -> String {
        return "Hello from Mock"
    }
}
```
```kotlin:kotlin
class GreetingRepositoryMock {
    fun greet(): String {
        return "Hello from Mock"
    }
}
```
では出力用コードをこんな風に書き換えて...

```swift:swift
var viewModel = GreetingViewModel(greetingRepository: GreetingRepositoryMock())

print(viewModel.greet())
```
```kotlin:kotlin
val viewModel = GreetingViewModel(greetingRepository = GreetingRepositoryMock())

println(viewModel.greet())
```
あれっ！？
「型が違います」みたいなこと言われてビルドできない！！

そうなんです。
`GreetingRepository`と`GreetingRepositoryMock`では型が違うので引数として渡せないんです。

かと言って、引数の型を`GreetingRepositoryMock`にしてしまうと、クラス完成後にまた修正しなくてはなりません。

そこでインターフェイス（プロトコル）を使います。

インターフェイス（プロトコル）というのは、クラスが実装すべきメソッドやプロパティを定義する型枠です。

### インターフェイス（プロトコル）
クラスには`greet`というメソッドがあれば良いので、こんな感じになります。

```swift:swift
protocol GreetingRepositoryProtocol {
    func greet() -> String
}
```
```kotlin:kotlin
interface GreetingRepositoryInterface {
    fun greet(): String
}
```

### ViewModel
引数の型を、クラスではなくインターフェイス（プロトコル）に変更します。
こうしておくと、インターフェイス（プロトコル）を実装したクラスであれば何でも渡せるようになります。

```swift:swift
class GreetingViewModel {
    private var greetingRepository: GreetingRepositoryProtocol

    init(greetingRepository: GreetingRepositoryProtocol) {
        self.greetingRepository = greetingRepository
    }

    func greet() -> String {
        return self.greetingRepository.greet()
    }
}
```
```kotlin:kotlin
class GreetingViewModel(private val greetingRepository: GreetingRepositoryInterface) {
    fun greet(): String {
        return this.greetingRepository.greet()
    }
}
```

### GreetingRepositoryクラス
インターフェイス（プロトコル）を実装したクラスになるように修正します。

```swift:swift
class GreetingRepository : GreetingRepositoryProtocol {
    func greet() -> String {
        return "Hello from Repository"
    }
}
```
```kotlin:kotlin
class GreetingRepository : GreetingRepositoryInterface {
    override fun greet(): String {
        return "Hello from Repository"
    }
}
```

### モックアップ
モックアップも、インターフェイス（プロトコル）を実装したクラスになるように修正します。

```swift:swift
class GreetingRepositoryMock : GreetingRepositoryProtocol {
    func greet() -> String {
        return "Hello from Mock"
    }
}
```
```kotlin:kotlin
class GreetingRepositoryMock : GreetingRepositoryInterface {
    override fun greet(): String {
        return "Hello from Mock"
    }
}
```
再び出力用コードを実行してみると...

### 結果
```
Hello from Mock
```
モックアップからの出力に変わりました！
本物を使いたい場合も、以前のように

```swift:swift
var viewModel = GreetingViewModel(greetingRepository: GreetingRepository())

print(viewModel.greet())
```
```kotlin:kotlin
val viewModel = GreetingViewModel(greetingRepository = GreetingRepository())

println(viewModel.greet())
```
とするだけで、`ViewModel`に変更を加えることなく、差し替えが可能になっています。

このように、**Dependency Injection (依存性の注入)** と **インターフェイス (プロトコル)** を合わせて使うことで、なかなか便利になったのが体感できたのではないかと思います。

これは、依存クラスの任意の挙動を再現する必要があるユニットテストなどに於いて、非常に有効なアプローチにもなります。

## ユニットテスト
現在時刻に対応した挨拶を返す`ViewModel`の`greet`メソッドを考えてみます。

仕様としては

- 6 ~ 11時 : "Good Morning"
- 12 ~ 17時 : "Good Afternoon"
- 18 ~ 翌5時 : "Good Evening"

を返すことにします。

実装してみるとこんな感じでしょうか。

```swift:swift
protocol TimeProviderProtocol {
    func hour() -> Int
}

class TimeProvider : TimeProviderProtocol {
    func hour() -> Int {
        return Calendar.current.component(.hour, from: Date())
    }
}

class GreetingViewModel {
    
    var timeProvider: TimeProviderProtocol
    
    init(timeProvider: TimeProviderProtocol) {
        self.timeProvider = timeProvider
    }
    
    func greet() -> String {
        switch self.timeProvider.hour() {
        case (let hour) where (6 <= hour && hour <= 11):
            return "Good Morning"
        case (let hour) where (12 <= hour && hour <= 17):
            return "Good Afernoon"
        case (let hour) where (18 <= hour && hour <= 23) || (0 <= hour && hour <= 5):
            return "Good Evening"
        default:
            return "Hello"
        }
    }
}

var viewModel = GreetingViewModel(timeProvider: TimeProvider())
print(viewModel.greet())
```
```kotlin:kotlin
import java.util.Calendar
import java.util.TimeZone

interface TimeProviderInterface {
    fun hour(): Int
}

class TimeProvider : TimeProviderInterface {
    override fun hour(): Int {
        val calendar = Calendar.getInstance().apply {
            timeZone = TimeZone.getTimeZone("Asia/Tokyo")
        }
        return calendar.get(Calendar.HOUR)
    }
}

class GreetingViewModel(private val timeProvider: TimeProviderInterface) {
    fun greet(): String {
        return when (this.timeProvider.hour()) {
            in 6..11 -> "Good Morning"
            in 12..17 -> "Good Afternoon"
            in 18..23, in 0..5 -> "Good Evening"
            else  -> "Hello"
        }
    }
}

val viewModel = GreetingViewModel(timeProvider = TimeProvider())
println(viewModel.greet())
```

この`ViewModel`が仕様通りに動くかどうかテストしたい場合、どうしますか？
わざわざその時間になるまで待って動かしますか？

それはさすがにありえないので、
任意の値を返せる`TimeProvider`のモックアップを作ってテストします。

こんな感じでしょうか。

```swift:swift
class TimeProviderMock : TimeProviderProtocol {
    
    private var _hour: Int
    
    init(hour: Int) {
        self._hour = hour
    }
    
    func hour() -> Int {
        return self._hour
    }
}
```
```kotlin:kotlin
class TimeProviderMock(private val hour: Int) : TimeProviderInterface {
    override fun hour(): Int {
        return this.hour
    }
}
```
コンストラクタに渡した値がそのまま`hour`メソッドの戻り値になります。

とすると、テストコードをこんな感じで書けます。
（本来は境界値とかもチェックすべきですが省略。）

```swift:swift
var viewModel: GreetingViewModel

viewModel = GreetingViewModel(timeProvider: TimeProviderMock(hour: 6))
assert(viewModel.greet() == "Good Morning")

viewModel = GreetingViewModel(timeProvider: TimeProviderMock(hour: 12))
assert(viewModel.greet() == "Good Afernoon")

viewModel = GreetingViewModel(timeProvider: TimeProviderMock(hour: 18))
assert(viewModel.greet() == "Good Evening")
```
```kotlin:kotlin
import org.junit.Assert

var viewModel: GreetingViewModel
    
viewModel = GreetingViewModel(timeProvider = TimeProviderMock(hour = 6))
Assert.assertEquals("Good Morning", viewModel.greet())
    
viewModel = GreetingViewModel(timeProvider = TimeProviderMock(hour = 12))
Assert.assertEquals("Good Afternoon", viewModel.greet())

viewModel = GreetingViewModel(timeProvider = TimeProviderMock(hour = 18))
Assert.assertEquals("Good Evening", viewModel.greet())
```

わざわざ時間になるのを待つことなく、そして`ViewModel`を修正することなく、
`ViewModel`のユニットテストを実行できるようになりました！

**DI (Dependency Injection)** って便利ですね！

以上です。