Fat Singletonを救いたい【Swiftでデザインパターン攻略 #2】

## はじめに
今回は「FatSingletonを救いたい」と題して、Singletonパターンについて書きました。
自分は、Singletonパターンを結構使うので、かなり馴染み深いデザインパターンです。

Singletonパターンは便利なのですが、気をつけて使わないと、何でもありコードになってしまうので、そのあたりを記事にできたらと思い書いてみました。

## この記事で学べること
- SwiftのSingletonパターンの書き方
- Singletonのメリット、デメリット
- `FatSingleton`にならないようにするためのリファクタリング **<- Point**
- UnitTestに都合の良いSingletonクラスにしておく **<- Point**

## Singletonパターンとは
#### 概要
特定のクラスのインスタンスがアプリケーションの中で1つしか生成されないことを保証するデザインパターンのことです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/580445/681d3152-77c7-f605-eb4a-3f48f268540c.png)
[Wikipediaより](https://ja.wikipedia.org/wiki/Singleton_パターン)

#### SwiftにおけるSingletonパターン
iOSアプリ開発においては、アプリ内の共通利用データを保持させるために用いられることが多いパターンです。
複数のクラスから参照が必要なものの、同じクラスの複数のインスタンスが乱立するとデータ整合性が保ちづらくなり、バグが発生することを防ぎたいときに使うことが多いです。

`SessionManager`や`LoginManager`があるあるです。

**メリット**
アプリ全体から利用できるため、アプリ内で共通利用したいデータをメモリに持たせたいときに活用することができます。
また、インスタンスが1つしか生成されないので、データの整合性が保証されます。

**デメリット**
メリットの性質がそのまま仇となるのですが、「どこからでも参照可能」という便利がゆえに、本来の役割を超えたプロパティやメソッドが書かれてしまい、`FatViewController`ならぬ`FatSingleton`が完成されてしまいます。

また、Singletonクラスに色々なプロパティやメソッドを追加しすぎたあまり、知らない間に密結合なコードになり、UnitTestがままならなくなります。(←体験談)

## Singletonパターンの雛形を実装する
```swift
// 1. 継承を防ぐためにfinalをつける
final class FatDataStore {
    // 2. 外部から参照するためにstatic変数でインスタンスを返すプロパティを用意する
    static let shared = FatDataStore()
    // 3. 外部からインスタンス化されないためにinitをprivateにする
    private init() {}
    
    // 4. 必要に応じて変数を追加する(この2つの値はAPIなどから取得した値をSetするものとする)
    var realMoney = 0
    var electricMoney = 0
    
    // 5. 必要に応じてメソッドを追加する
    func calcTotalMoney() -> Int {
        return realMoney + electricMoney
    }
    
    func calcRealMoneyRatio() -> Int {
        return realMoney / calcTotalMoney() * 100
    }
    
    func calcBalance(payMoney: Int) -> Int {
        return calcTotalMoney() - payMoney
    }
}

// 利用する時
print(FatDataStore.shared.realMoney)   // 0
FatDataStore.shared.realMoney = 1000
print(FatDataStore.shared.realMoney)   // 1000
```

1〜3は、Singletonクラスを作る上で定番のパターンで、とりあえずこのようにしておけばSigletonパターンとして使えると言って問題ないです。

## "Fat" Singleton問題
結論から言うと、上記のSingletonクラスは`FatSingleton`になる危険性があります。
なぜなら、上記のSingletonクラスはあくまで`DataStore`としての位置づけですが、自身のプロパティを用いた計算、アクセス元から渡された引数を用いた計算もSingletonクラスで行われています。つまりこのクラスは、`DataStore`としての責務を超えて「なんでも実行クラス」になってしまっていると言えます。

このように、Singletonはアプリ内で共有する利用頻度の高いプロパティを持つことから、それに関するメソッドがSingletonクラス内に乱立してしまうという危険性があるということです。

というわけで、計算に関するメソッドはちゃんと計算クラスを定義することで`FatSingleton`を防ぐことができます。

```swift
class Calcrator {
    
    let dataStore: FatDataStore
    
    // Singletonクラスのプロパティを利用したければ初期化時にセットする
    init(dataStore: FatDataStore) {
        self.dataStore = dataStore
    }
    
    func calcTotalMoney() -> Int {
        return dataStore.realMoney + dataStore.electricMoney
    }
    
    func calcRealMoneyRatio() -> Int {
        return dataStore.realMoney / calcTotalMoney() * 100
    }
    
    func calcBalance(payMoney: Int) -> Int {
        return calcTotalMoney() - payMoney
    }
}
```

## UnitTestを考慮してもう一歩踏み込んでみる
FatSingleton問題以外のもう1つのデメリットであるこれも解消してしまいましょう。
>また、Singletonクラスに色々なプロパティやメソッドを追加しすぎたあまり、知らない間に密結合なコードになり、UnitTestがままならなくなります。(←体験談)

サンプルコードの`realMoney`や`electricMoney`がAPIから取得した値をセットするなどという仕様の場合、UnitTest時には、本来期待される値が入っていないことが多いです。

全財産の合計計算、残高計算のテストをしたいのに、わざわざAPIを投げてサーバーから値を受け取ってテストするというのは、正しい単体テストのあり方ではありません。

そんな問題点を解決するためのコードがこれです。

```swift
// DataStoreProtocolを準拠させる
final class SlimDataStore: DataStoreProtocol {
    static let shared = SlimDataStore()
    private init() {}
    
    var realMoney: Int = 0
    var electricMoney: Int = 0
}

class Calcrator {
    
    let dataStore: DataStoreProtocol
    
    // 引数で受け取る型を「DataStoreProtocol」に変更する
    init(dataStore: DataStoreProtocol) {
        self.dataStore = dataStore
    }
    
    func calcTotalMoney() -> Int {
        return dataStore.realMoney + dataStore.electricMoney
    }
    
    func calcRealMoneyRatio() -> Int {
        return dataStore.realMoney / calcTotalMoney() * 100
    }
    
    func calcBalance(payMoney: Int) -> Int {
        return calcTotalMoney() - payMoney
    }
}
```

まず、`DataStoreProtocol`を定義して、Singletonクラスに必ず必要なプロパティやメソッドを確定させます。
そのプロトコルを`SlimDataStore`というSingletonへ準拠させます。

最後に、Singletonを利用したいクラスでは、`SlimDataStore`を直接受け取る形ではなく、`DataStoreProtocol`を初期化時の引数として受け取るようなロジックへ変更します。

こうすることで、UnitTestの時は、`DataStoreProtocol`を準拠した`TestDataStore`クラスに差し替えるだけでいいので、計算テストが単体テストとして実行できるようになります。

```swift
class TestDataStore: DataStoreProtocol {
    var realMoney: Int = 11111
    var electricMoney: Int = 88888
}

let calcTest = Calcrator(dataStore: TestDataStore())
print(calcTest.calcTotalMoney())    // 99999
```

こんな感じです。

## おわりに
Singletonパターンはデザインパターンについて知る前から、プロジェクトでよく使われていました。
なんで、わりと思い入れのあるデザインパターンで長くなってしまいましたが、皆さんの参考になれば幸いです。

## 参考にさせていただいた記事
https://qiita.com/TheAtlasEngine/items/cbf75a0fcf75f805c420