【Unity】Unityで学ぶデザインパターン01: AbstractFactory パターン【デザパタ】

# はじめに
様々な言語で「デザインパターン」の本が世の中にありますが、筆者個人の経験では
`いまいちピンとこない例` `いまいちピンとこないコード` で説明されてることが多く、
`結局これっていつ使うの？` という疑問に答えるには仕事仲間等との議論をしないと
辿り着けないことが多々ありました。

そこで特に「ゲーム開発ではどう使うか？」にフォーカスを当てて、実践的な例を交えて
デザインパターンの説明の需要があると思い記事を作りました。

## デザインパターンを学ぶ理由

デザインパターンを学ぶ理由としては
1. 車輪の再発明の防止
1. 長文で読みにくいコード（可読性の低いコード）を減らす
1. コードを疎結合にして変更に強くなる(変更時のコスト・変更箇所を減らす)
1. モジュールとして使いまわせるように、コードの再利用性を高める
といった効果を期待できます。

## 対象読者
Unity 全くの初心者(インストールしただけで触ったことがないような方)はお断りです。
最低限以下のことは理解・経験を積んでおくことが必須になります。

- MonoBehaviour 継承クラスでコードを書いたことがある
- C# のピュアクラスを用いた自作クラスを作ったことがある
- クラスの継承という概念は知っている

そのため、`脱・初心者` `中級者へのステップアップ` として `デザインパターンを学ぶ` のが良いと思います。


# デザパタ記事リンク
## 生成系
1. [AbstractFactory パターン](https://qiita.com/Cova8bitdot/items/e863cecf48c0529ba05a)（本記事)
1. [Builder パターン](https://qiita.com/Cova8bitdot/items/9dcdfeb0641bc774b1f8)
1. [FactoryMethod パターン](https://qiita.com/Cova8bitdot/items/61a38fd51735f321e564)
1. [Prototype パターン](https://qiita.com/Cova8bitdot/items/0bc251bc36201985c204)
1. [Singleton パターン](https://qiita.com/Cova8bitdot/items/29b7064c7472a6f34972)

## 構造系
1. [Adapter パターン](https://qiita.com/Cova8bitdot/items/f09776957073cf64902b)
1. [Bridge パターン](https://qiita.com/Cova8bitdot/private/5b528216952c74436045)
1. [Composite パターン](https://qiita.com/Cova8bitdot/items/1c57d856027a33e99eb0)
1. [Decorator パターン](https://qiita.com/Cova8bitdot/items/e1373a756b3dfd86c920)
1. [Facade パターン](https://qiita.com/Cova8bitdot/items/bdf0976bdfa83e57f121)
1. [Flyweight パターン](https://qiita.com/Cova8bitdot/items/7f1f1f04b46ebaf43c5e)
1. [Proxy パターン](https://qiita.com/Cova8bitdot/items/8a27137812e103ec1ee3)

## 様態・ふるまい系
1. [Chain of Responsibility パターン](https://qiita.com/Cova8bitdot/items/66c62dd96adffda7e72e)
1. [Command パターン](https://qiita.com/Cova8bitdot/items/9a88f7fc0782c729f97c)
1. [Interpreter パターン](https://qiita.com/Cova8bitdot/items/a871c1de813d14049934)
1. [Iterator パターン](https://qiita.com/Cova8bitdot/items/302f19863cc3b2235625)
1. [Mediator パターン](https://qiita.com/Cova8bitdot/items/be33b97f35c84e814df7)
1. [Memento パターン](https://qiita.com/Cova8bitdot/items/ae7e6f5d2da7b07d379b)
1. [Observer パターン](https://qiita.com/Cova8bitdot/items/632782ad5264baf6a366)
1. [State パターン](https://qiita.com/Cova8bitdot/items/ab9fc16ac3a6657fdce0)
1. [Strategy パターン](https://qiita.com/Cova8bitdot/items/28dd773075cf6e9fefee)
1. [TemplateMethod パターン](https://qiita.com/Cova8bitdot/items/f0bed164ef6563b489d0)
1. [Visitor パターン](https://qiita.com/Cova8bitdot/items/15c56d40435adf63891d)


# AbstractFactory　パターンについて

AbstractFactory(アブストラクトファクトリー）パターンとは `同一のものを複数回、生成がリクエストされるようなケース` で用いられるデザインパターンです。名称が長いので Abstract(抽象)を省略して Factoryパターンを呼称する場合もありますが、別記事で説明するFactoryMethod パターンとどちらを指すのか？という問題もあるので、省略するときはご注意ください。

さて、名前の通り「抽象的な工場」ですが、これは `工場の抽象クラスを作ると便利` 程度の認識で大丈夫です。
具体的なFactory(SimpleFactory などとも呼ばれたりする) を考えても良いのですが、もう少し使いまわせる仕組みとして1段階抽象化レベルを上げて「汎用的な工場に必要な仕組み」を考えた方が再利用性が高いコードを作ることが可能になります。


## ゲームにおける AbstractFactoryパターン
ではゲームにおける `工場` とは何でしょうか？ [Satisfactory](https://store.steampowered.com/app/526870/Satisfactory/?l=japanese) のような工場シミュレーションゲームそのものではありません（もちろん内部実装ではAbstractFactoryパターンを使っているとは思いますが）。

いくつか実例を考えてみましょう。
| ゲームカテゴリ | 利用例 |
|--- |--- |
| (弾幕)シューティングゲーム | 自機の弾の生成役(スポナー) |
| アクションゲーム| 無限湧きする敵のスポナー |
| 戦略シミュレーション, RTS | ユニット作成時のスポナー |


このように考えるといわゆる `スポナー` と呼ばれる機能= `工場` と考えて良いでしょう。

## 工場の特色
工場の特色としては以下のとおりです。
- 生成リクエストが来たら納品物を工場内で作成→納品
- リクエスト側は生成過程を見ることが出来ない（仮に工場見学できたとしても生成過程に口出しは出来ない)

## AbstractFactory パターンを使わない実装
``` NonFactoryEnemy.cs
using System.Collections.Generic;
using UnityEditor;

public class Enemy : MonoBehaviour
{
    /* parameters */
    public string Name{get;set;}
    public int Hp{get;set;}
    public int MaxHp{get;set;}
    public int Mp{get;set;}
    public int MaxMp{get;set;}
    public int Atk{get;set;}
    public int Def{get;set;}
    public int Speed{get;set;}
    public Skill EnemySkill{get;set;}
}

public class GameCycle : MonoBahaviour
{
    const int MAX_ENEMY_COUNT = 10;
    List<Enemy> enemys = new List<Enemy>(MAX_ENEMY_COUNT); // Listのキャパシティは要件に応じて設定しようね
    void Update()
    {
        if(enemys.Count < MAX_ENEMY_COUNT )
        {
            var e = (enemys.Count % 2) == 0 
                        ? Instantiate<Enemy>(SKILLED_PREFAB_PATH)
                        : Instantiate<Enemy>(NON_SKILL_PREFAB_PATH);
            e.Name = "Enemy_"+enemys.Count;
            e.Hp = 10;
            e.MaxHp = 10;
            e.Mp = 1;
            e.MaxHp = 1;
            e.Atk = 2;
            e.Def = 1;
            e.Speed =1;
            e.EnemySkill = (enemys.Count % 2) == 0 ? new EnemySkill_Fire() : null ;

            enemys.Add(e);
        }
    }
}

```

このように敵が一定数以下になったら敵を生成するコードを考えると、パラメータ設定をゲームのメインサイクルに書くことになります。
また、EnemySkill の部分ですが、今回はSkill クラスということで、プレイヤーSkill と敵用Skill が同じ派生元の場合、Enemy.EnemySkill の部分に最悪プレイヤー用Skill を設定することも可能です。

上記からわかるとおり、以下のような課題があります。
- パラメータ設定を逐一設定するコードを書かないといけない
- 間違った入力が混入する可能性がある


## AbstractFactory を使った実装

今回の件を考えると工場は `Spawn()` という生成用メソッド(生成API) があると言えるでしょう。

つまりは以下のような形です。
```mermaid
classDiagram

    class BaseSpawnObject{
        <<abstract>>
    }

    class BaseSpawner{
        <<abstract>>
        + Spawn(SpawnParameter p)* : BaseSpawnObject
    }
    BaseSpawnObject <.. BaseSpawner
```

これを継承して「スキルを持った敵」と「スキルを持たない敵」の生成クラスを考えてみましょう。
クラス図を作ると以下のような形です。

```mermaid
classDiagram
    class BaseSpawnObject{
        <<abstract>>
    }

    class BaseSpawner{
        <<abstract>>
        + Spawn(SpawnParameter p)* : BaseSpawnObject
    }
    BaseSpawnObject <.. BaseSpawner

    class NonSkillEnemySpawner{
    }
    BaseSpawner <|-- NonSkillEnemySpawner
    class SkilledEnemySpawner{
    }
    BaseSpawner <|-- SkilledEnemySpawner

    class Enemy{
        + Name: string
        + Hp: int
        + MaxHp: int
        + Mp: int
        + MaxMp: int
        + Atk: int
        + Def: int
        + Speed: int
        + EnemySkill: Skill
    }
    BaseSpawnObject <|-- Enemy

    Enemy <.. NonSkillEnemySpawner
    Enemy <.. SkilledEnemySpawner
```

``` Spawner.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class BaseSpawnParameter
{
}
public abstract class BaseSpawnObject : MonoBehaviour
{
}

public abstract class BaseSpawner
{
    public abstract BaseSpawnObject Spawner(TParam p) where TParam:BaseSpawnParameter
}

```

``` Enemy.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;

public class Enemy : BaseSpawnObject
{
    /* parameters */
    public string Name{get;set;}
    public int Hp{get;set;}
    public int MaxHp{get;set;}
    public int Mp{get;set;}
    public int MaxMp{get;set;}
    public int Atk{get;set;}
    public int Def{get;set;}
    public int Speed{get;set;}
    public Skill EnemySkill{get;set;}
}
public class EnemySpawnParam : BaseSpawnParameter
{
    /* parameters */
    public string Name{get;set;}
    public int Hp{get;set;}
    public int MaxHp{get;set;}
    public int Mp{get;set;}
    public int MaxMp{get;set;}
    public int Atk{get;set;}
    public int Def{get;set;}
    public int Speed{get;set;}
    public Skill EnemySkill{get;set;}
}

public class NonSkillEnemySpawner : BaseSpawner
{
    private static readonly PREFAB_PATH = "Resources/NonSkillEnemy.prefab";

    public override BaseSpawnObject Spawner(TParam p) where TParam:BaseSpawnParameter
    {
        var enemyParam = p as EnemySpawnParam;
        var e = GameObject.Instantiate<Enemy>(PREFAB_PATH);
        Assert.IsNotNull(e, "[NonSkillEnemySpawner] e is NULL");
        e.Name = enemyParam.Name;
        e.Hp = enemyParam.Hp;
        e.MaxHp = enemyParam.MaxHp;
        e.Mp = enemyParam.Mp;
        e.MaxMp = enemyParam.MaxMp;
        e.Atk = enemyParam.Atk;
        e.Def = enemyParam.Def;
        e.Speed = enemyParam.Speed;
        e.EnemySkill = null;
        return e;
    }
}

public class SkilledEnemySpawner : BaseSpawner
{
    private static readonly PREFAB_PATH = "Resources/SkilledEnemy.prefab";

    public override BaseSpawnObject Spawner(TParam p) where TParam:BaseSpawnParameter
    {
        var enemyParam = p as EnemySpawnParam;
        var e = GameObject.Instantiate<Enemy>(PREFAB_PATH);
        Assert.IsNotNull(e, "[NonSkillEnemySpawner] e is NULL");
        e.Name = enemyParam.Name;
        e.Hp = enemyParam.Hp;
        e.MaxHp = enemyParam.MaxHp;
        e.Mp = enemyParam.Mp;
        e.MaxMp = enemyParam.MaxMp;
        e.Atk = enemyParam.Atk;
        e.Def = enemyParam.Def;
        e.Speed = enemyParam.Speed;
        e.EnemySkill = enemyParam.Skill;
        return e;
    }
}
```

```Main.cs
public class GameCycle : MonoBahaviour
{
    const int MAX_ENEMY_COUNT = 10;
    NonSkillEnemySpawner nonSkillEnemySpawner = new NonSkillEnemySpawner();
    SkilledEnemySpawner skillEnemySpawner = new SkilledEnemySpawner();
    EnemySpawnParam spawnParam = new EnemySpawnParam();
    List<Enemy> enemys = new List<Enemy>(MAX_ENEMY_COUNT); // Listのキャパシティは要件に応じて設定しようね

    void Awake()
    {
            spawnParam.Hp = 10;
            spawnParam.MaxHp = 10;
            spawnParam.Mp = 1;
            spawnParam.MaxHp = 1;
            spawnParam.Atk = 2;
            spawnParam.Def = 1;
            spawnParam.Speed =1;
            spawnParam.EnemySkill = new EnemySkill_Fire();
    }
    void Update()
    {
     
        if(enemys.Count < MAX_ENEMY_COUNT )
        {
            spawnParam.Name = "Enemy_"+enemys.Count;
            var e = GetSpawner(enemys.Count).Spawn(spawnParam);
            enemys.Add(e);
        }
    }

    BaseSpawner GetSpawner(int count)
    {
        if( count == 0 ) return skillEnemySpawner;
        else return  nonSkillEnemySpawner
    }
}

```

このように工場自体を分割すれば、スキル無しの敵にスキルが誤って設定されることは防止できます。
さらに、使用するPrefab を取り違えるような間違いもかなり防げるようになっています。

ゲームロジックを書くメインのコードが非常にスッキリしました。

確かにFactoryパターンであるSpawner を作る手間はあります。
しかし、派生系を沢山作る場合に、if文でどのパラメータを書き換えるかを考えるよりも、どのスポナー(Fatory) を使うかを考えるだけ(GetSpawner() のところのみ) に修正コストを減らすことが可能なため、バリエーションが多くなりがちなところで強力に効果を発揮します。

# まとめ
AbstractFactory パターンを使うと単に同じObjectを複数生成するときに、`生成したい側は生成関数を呼ぶだけ` `生成担当側にロジックを分離` することが出来ます。

特にゲームにおいては `ObjectPool と組み合わせてObjectの再生成` に使われることが多く、「弾」や「敵」の生成などにクリティカルに使われるデザインパターンであることがわかりました。