Swiftでデザインパターンを振り返る - Abstract factory pattern

デザインパターンについてもう一度振り返ろうと思いしばらく連載しようと思います。
今回は、*Abstract factory patter*です。

# Abstract factory pattern
<img width="645" alt="スクリーンショット 2020-01-29 22.45.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114168/8808d4af-be6d-ef9e-17c5-544f534fd7e8.png">
直訳すると*抽象的な工場パターン*となります。
> お互いに関連したり依存し合うオブジェクト群を、その具象クラスを明確にせずに生成するためのインターフェースを提供することを目的とする。
- オブジェクト指向における再利用のためのデザインパターンより

特定のオブジェクトを生成するための共通インターフェースを継承した**オブジェクト生成用のクラス**を介して特定のオブジェクトを生成することで、オブジェクト生成時の責務の切り分けと過程の秘匿化が容易になります。

## ベースとなるサンプル
```swift
class AbstractFactoryViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        /// john cooper works
        let miniJcw = Mini(power: PowerSpec.johnCooperWorks.rawValue,
                        bodyColor: "ブレイジングレッド")
        sayCarStatus(with: miniJcw)
    }
    
    /// 車のスペックをPrintします
    func sayCarStatus(with mini: Mini) {
        print("このMINIは、馬力が\(mini.power)。 車体色が、\(mini.bodyColor)です。")
    }

}

/// MINI
class Mini {
    /// 馬力
    let power: Int
    /// 車体色
    let bodyColor: String
    
    init(power: Int, bodyColor: String) {
        self.power = power
        self.bodyColor = bodyColor
    }
}
```
今回は上記の例を用います。
``viewDidLoad``のタイミングで、``Mini``オブジェクトを生成しています。
オブジェクト生成後、``func sayCarSpec()``を呼び車のスペックをプリントします。
現状では、View側にオブジェクト生成に必要な馬力と車体色がハードコーディングされています。
現状でも期待通りの車のスペックをプリントすることができます。

## 更に別の車のスペックも欲しい場合

```swift
class AbstractFactoryViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        /// john cooper works
        let miniJcw = Mini(power: PowerSpec.johnCooperWorks.rawValue,
                        bodyColor: "ブレイジングレッド")
        sayCarStatus(with: miniJcw)
        /// one
        let miniOne = Mini(power: PowerSpec.one.rawValue,
                           bodyColor: "ブリティッシュグリーン")
        sayCarStatus(with: miniOne)
    }
    
    /// 車のスペックをPrintします
    func sayCarStatus(with mini: Mini) {
        print("このMINIは、馬力が\(mini.power)。 車体色が、\(mini.bodyColor)です。")
    }

}

/// MINI
class Mini {
    /// 馬力
    let power: Int
    /// 車体色
    let bodyColor: String
    
    init(power: Int, bodyColor: String) {
        self.power = power
        self.bodyColor = bodyColor
    }
}
```

MINI OneのスペックをPrintするために、新しいMINIオブジェクトを生成するようにしました。
現状では、View側でオブジェクトの生成に必要な値がハードコードされています。もし、MINIオブジェクトの生成に必要な引数などが増えた場合、``JohnCooperWorks``オブジェクトと``miniOne``オブジェクトに渡す引数を増やす必要があり、オブジェクト側の変更が難しくなってしまいます。
また、View側の責務は受け取ったデータをUIに反映することであるため、データの生成・取得に関しては責務外のことを上記のサンプルでは行っています。

## Abstract factory patternを適用する
```swift
class AbstractFactoryViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        /// john cooper works
        let miniJcw = JohnCooperWorksFactory().create()
        sayCarStatus(with: miniJcw)
        /// one
        let miniOne = OneFactory().create()
        sayCarStatus(with: miniOne)
    }
    
    /// 車のスペックをPrintします
    func sayCarStatus(with mini: Mini) {
        print("このMINIは、馬力が\(mini.power)。 車体色が、\(mini.bodyColor)です。")
    }

}

/// Factoryのルールを定義したProtocol
protocol MiniFactoryProtocol {

    /// Miniオブジェクトを生成する
    func create() -> Mini

}

/// JohnCooperWorksオブジェクトを生成するオブジェクト
class JohnCooperWorksFactory: MiniFactoryProtocol {

    /// JohnCooperWorksの馬力
    private let johnCooperWorks = 231
    
    /// Miniオブジェクトを生成する
    func create() -> Mini {
        return Mini.init(power: johnCooperWorks,
                         bodyColor: "ブレイジングレッド")
    }
    
}

/// Mini Oneオブジェクトを生成するオブジェクト
class OneFactory: MiniFactoryProtocol {
    /// Oneの馬力
    private let one = 102
    
    func create() -> Mini {
        return Mini.init(power: one,
                         bodyColor: "ブリティッシュグリーン")
    }
}
```
実際に*Abstract factory pattern*を適用してみた例が上記になります。
適用前のサンプルでは、View側でMiniオブジェクト生成に必要な値をMiniオブジェクトのイニシャライズ時の引数として渡してMiniオブジェクトを生成していました。
それを車のグレードごとにMiniオブジェクトを生成するファクトリークラスを用意し、グレードごとの馬力もファクトリークラス内で保持するように変更しています。

また、protocolを定義することで、どのFactoryクラスにも``func create() -> Mini``メソッドが定義されていることを保証しています。

## メリット
### 柔軟な変更性
例えば、突如オプションパッケージを車に設定できるようにする仕様変更があったとします。
そんな仕様変更を受け入れるためにMiniオブジェクトを下記のように変更することになりました。

```swift
/// MINI
class Mini {
    /// 馬力
    let power: Int
    /// 車体色
    let bodyColor: String
    /// オプションパッケージ名
    let optionPackageName: String?
    
    init(power: Int, bodyColor: String, packageName: String? = nil) {
        self.power = power
        self.bodyColor = bodyColor
        self.optionPackageName = packageName
    }
}
```
オプションパッケージ名のプロパティが増えたのと、イニシャライザーの引数が追加されています。
イニシャライザーの引数は、従来の実装を考慮し``packageName``を設定しない場合はデフォルトの値を``nil``とします。

```swift
class AbstractFactoryViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        /// john cooper works
        let miniJcw = Mini(power: PowerSpec.johnCooperWorks.rawValue,
                        bodyColor: "ブレイジングレッド", packageName: "ペッパーパッケージ")
        sayCarStatus(with: miniJcw)
        /// one
        let miniOne = Mini(power: PowerSpec.one.rawValue,
                           bodyColor: "ブリティッシュグリーン")
        sayCarStatus(with: miniOne)
    }
    
    /// 車のスペックをPrintします
    func sayCarStatus(with mini: Mini) {
        print("このMINIは、馬力が\(mini.power)。 車体色が、\(mini.bodyColor)です。")
        if let packageName = mini.optionPackageName {
            print("パッケージは、\(packageName)が標準装備です。")
        }
    }

}
```
JohnCooperWorkモデルのみにパッケージ名をもたせるためにView側で行っているオブジェクトのイニシャライズ時に引数として新たにパッケージ名を追加で渡す必要が発生します。
このような変更が生じるたびにView側に記載されたオブジェクト生成の処理を変更する必要が発生するため、改修が難しくなってしまいます。

Abstract factory patternを適用し、JohnCooperWorksのステータス値を持ったMINIオブジェクトを生成する場合は、``JohnCooperWorksFactory``クラス内でMINIオブジェクトのイニシャライズ時にパッケージ名を引数で追加するようにするだけで実現できます。

```swift

/// JohnCooperWorksオブジェクトを生成するオブジェクト
class JohnCooperWorksFactory: MiniFactoryProtocol {

    /// JohnCooperWorksの馬力
    private let johnCooperWorks = 231
    
    /// Miniオブジェクトを生成する
    func create() -> Mini {
        return Mini.init(power: johnCooperWorks,
                         bodyColor: "ブレイジングレッド",
                         packageName: "ペッパーパッケージ"
        )
    }
    
}

/// Mini Oneオブジェクトを生成するオブジェクト
class OneFactory: MiniFactoryProtocol {
    /// Oneの馬力
    private let one = 102
    
    func create() -> Mini {
        return Mini.init(power: one,
                         bodyColor: "ブリティッシュグリーン")
    }
}
```
JohnCooperWorksオブジェクト、Mini Oneオブジェクトを生成するのに必要な値やオブジェクトを変更する必要がある場合は、それぞれのFactoryクラスの処理を変更するだけで済むようになるため後からの変更性が容易になります。
また、オブジェクトを生成するための処理が完全にFactoryクラス内に閉じられ過程が隠蔽されることで責務の分担も明確になります。

## デメリット
### 新たな部品の種類に対応することが少し面倒
Factoryクラスは、オブジェクトを生成するために必要な値やオブジェクトの集まりを固定化しているだけに過ぎないため、上記のような追加のプロパティが定義されたときにFactoryクラスの修正や今回は例として扱っていませんが子のオブジェクトクラスまで修正する必要が発生します。
ただ、これに関しては拡張性を考慮して実装すればこと足りることなのであまり大きな問題にはなりません。
初期実装の段階から拡張する必要が生まれた場合は、デフォルト引数などを用いて追加プロパティを初期化することも出来ますので考え方次第で、デメリットは解消することが出来ます。

## 実務での活用事例など
シンプルかつイメージが少し湧きづらいサンプルを用いて説明してきましたが、実際に実務で普段使っているイメージとしては、Translatorクラスなどが該当すると思います。

```swift
class AIUFilePathDataModelTranslator {
    
    func translate (with value: String, query: String) -> [AIUFilePathDataModel] {
        let lines = value.components(separatedBy: .newlines)
        return lines.reduce([AIUFilePathDataModel]()) {
            var result = $0
            guard let pathData: AIUFilePathDataModel = createPathData(with: $1, query: query) else {
                return result
            }
            result.append(pathData)
            return result
        }
    }
}
```
上のコードは、趣味で開発しているframework内で利用しているTranslatorクラスです。
FlashAirから返却されるcsv形式の文字列を改行コードでスプリットして配列化、各配列の値から更に分解し、``AIUFilePathDataModel``オブジェクトを生成して複数のオブジェクトを入れた配列として結果を返却するものです。
やってることとしては、**必要な値を受け取り、イニシャライズに必要な値に変換して、オブジェクトをイニシャライズして返却する**ということです。
これがAbstract factory patternだと自分の中では解釈しています。

# おわりに
普段当たり前に書いているコードですが、改めてデザインパターンに向き合うとこんなとこにこれが活用されているんだなと振り返る機会になりました。
知見の洗い直しも兼ねてしばらく振り返りの機会として連載続けていきたいなと思います。
多分認識の違いもあったりすると思うので、マサカリ飛ばしてください！