型パラメータインジェクション - Type Parameter Injection(Swift)

## はじめに

Swiftにおける型パラメータインジェクションを紹介します。

## 環境

- OS：macOS Monterey 12.3.1
- Xcode：13.3.1 (13E500a)
- Swift：5.6

## 「型パラメータインジェクション」とは？

「型パラメータインジェクション（Type Parameter Injection）」はDIの手法のひとつで、ジェネリクスの型引数を使って依存性を注入することです。

```swift:FooProtocol.swift
protocol FooProtocol {
    static func foo()
}
```

```swift:Bar.swift（型パラメータインジェクション）
final class Bar<F: FooProtocol> { // 型引数で依存性を注入する
    func bar() {
        F.foo()
    }
}
```

この手法はTwitterで教えていただきました。
本記事ではkoherさんに合わせて「型パラメータインジェクション」と呼んでいます。

https://twitter.com/koher/status/1519454217383014401

正式名称があるかどうかはわかりません。

## 他のインジェクションとの比較

「バスタードインジェクション（Bastard Injection）」 [^bastard-injection] とコンストラクタインジェクション、型パラメータインジェクションを比較します。

[^bastard-injection]: イニシャライザのデフォルト引数でインスタンスを注入すること。

### バスタードインジェクション

まずはバスタードインジェクションを紹介します。

`Foo` をシングルトンにして、`Bar` のイニシャライザのデフォルト引数に指定します。

```swift:FooProtocol.swift
protocol FooProtocol {
    func foo()
}
```

```swift:Foo.swift
struct Foo {
    static let shared = Foo()

    private init() {} // シングルトンにするため、イニシャライザを外部に公開しない
}

extension Foo: FooProtocol {
    func foo() {
        print("Foo")
    }
}
```

```swift:Bar.swift（バスタードインジェクション）
final class Bar {
    private let foo: any FooProtocol

    init(foo: any FooProtocol = Foo.shared) { // イニシャライザのデフォルト引数で依存性を注入する
        self.foo = foo
    }

    func bar() {
        foo.foo()
    }
}
```

`Bar().bar()` を実行すると、 `Foo` と出力されます。

```swift
Bar().bar() // "Foo"
```

`Foo` をモックに差し替えるのが容易なため、 `Bar` はテスタブルであり、大きな問題はありません。
しかし以下のデメリットがあります。

- 存在型（ `any` ）を使っている [^existential-type]
- 状態に依存しないことが保証されていない
  - `Foo` のインスタンスを生成しているため
- シングルトンにするためのボイラーテンプレートが存在する

[^existential-type]: 基本的に存在型は使わないのが望ましい。https://heart-of-swift.github.io/protocol-oriented-programming/#:~:text=プロトコルを「型として」ではなく「制約として」使用することを優先する

### コンストラクタインジェクション

「存在型を使っている」デメリットを解消するため、 `FooProtocol` プロトコルを `Bar` の型引数に指定します。

```diff_swift:Bar.swift（コンストラクタインジェクション）
- final class Bar {
-   private let foo: any FooProtocol
+ final class Bar<F: FooProtocol> {
+   private let foo: F

-   init(foo: any FooProtocol = Foo.shared) { // イニシャライザのデフォルト引数で依存性を注入する
+   private init(foo: F) {
        self.foo = foo
    }
+ 
+   convenience init() where F == Foo {
+       self.init(foo: .shared)
+   }

    func bar() {
       foo.foo()
    }
}
```

これで存在型がなくなり、デメリットがひとつ減りました。

- ~~存在型（ `any` ）を使っている~~
- 状態に依存しないことが保証されていない
  - `Foo` のインスタンスを生成しているため
- シングルトンにするためのボイラーテンプレートが存在する

しかしボイラーテンプレートが増えてしまいました。
コンビニエンスイニシャライザを使ったデフォルト値の指定方法の詳細は、以下の記事をご参照ください。

https://qiita.com/uhooi/items/336935761a631f5ea9be#デフォルト値を持つメソッドを追加する

### 型パラメータインジェクション

残り2つのデメリットを一気に解消するのが、型パラメータインジェクションです。

`Foo` の `foo()` メソッドは文字列を標準出力しているだけなので、静的にできます。
静的にできるかプロトコルからはわからないこともありますが、今回はわかるものとします。

```diff_swift:FooProtocol.swift
protocol FooProtocol {
-   func foo()
+   static func foo()
}
```

`Foo` のインスタンスを生成する必要がなくなったので、構造体から列挙型に変更します。

```diff_swift:Foo.swift
- struct Foo {
+ enum Foo {
-   static let shared = Foo()
- 
-   private init() {} // シングルトンにするため、イニシャライザを外部に公開しない
}

extension Foo: FooProtocol {
-   func foo() {
+   static func foo() {
        print("Foo")
    }
}
```

`Foo` のインスタンスが生成できなくなり、状態に依存しないことが保証されます。

`Bar` から不要になった定数やイニシャライザを削除します。

```diff_swift:Bar.swift（型パラメータインジェクション）
final class Bar<F: FooProtocol> {
-   private let foo: F
- 
-   private init(foo: F) {
-       self.foo = foo
-   }
- 
-   convenience init() where F == Foo {
-       self.init(foo: .shared)
-   }
- 
    func bar() {
-       foo.foo()
+       F.foo()
    }
}
```

`Foo` を外部から注入するためのボイラープレートをすべて削除できました。

`Bar<Foo>().bar()` を実行すると、 `Foo` と出力されます。

```swift
Bar<Foo>().bar() // "Foo"
```

これで型パラメータインジェクションの実装は完成です。

型引数にはデフォルトの型を指定できないため、呼び出し時に型を明示的に指定する必要があります。
しかし呼び出す側（ `Bar` ）が呼び出される側（ `Foo` ）に依存しなくなるので、一般的にはメリットといえます。

モックに差し替えるのも簡単です。

```swift:FooMock.swift
enum FooMock {
}

extension FooMock: FooProtocol {
    static func foo() {
        print("FooMock")
    }
}
```

```swift
Bar<FooMock>().bar() // "FooMock"
```

これで最初に挙げたデメリットがすべて解消されました。

- ~~存在型（ `any` ）を使っている~~
- ~~状態に依存しないことが保証されていない~~
  - ~~`Foo` のインスタンスを生成しているため~~
- ~~シングルトンにするためのボイラーテンプレートが存在する~~

## おわりに

型パラメータインジェクションの紹介でした。
コードがスッキリするため、インスタンスを生成する必要がないときは、積極的に使ってもいい手法だと思います。

## 参考リンク

- https://twitter.com/koher/status/1514138781049241604
- https://twitter.com/koher/status/1501426736507985922