ゲームプログラマのための設計：Adapterパターンと、その先のExternal Polymorphismパターン

[ゲームプログラマのための設計シリーズ](https://qiita.com/kobitnex/items/93397d985682c806884c)：デザインパターン編の記事です。

# 概要
* Adapterパターンを汎用化していくと、External Polymorphismパターンにたどり着く
* それなりに複雑なので、型の追加が頻繁なときのみ使おう

# 本文

チームメイトが、IFighterインターフェースを使った戦闘システムを作ってくれました！
```cpp: 戦闘システム
class IFighter
{
public:
	virtual ~IFighter() = default;
	virtual void attack() = 0;   // 攻撃
	virtual void defence() = 0;  // 防御
};

void fight(IFighter& fighter)
{
	fighter.attack();
	fighter.defence();
}
```
ここに、IFighterとは関係ないPlayer/Enemyクラスがあります。

```cpp
class Player {};
class Enemy {};

// 攻撃時の処理
void PlayerAttack(Player&) { Print("Player's Attack"); }
void EnemyAttack(Enemy&)   { Print("Enemy's Attack"); }

// 防御時の処理
void PlayerDefence(Player&){ Print("Player's Defence"); }
void EnemyDefence(Enemy&)  { Print("Enemy's Defence"); }
```
これらをfight関数に適用するにはどうしたらよいでしょうか。

## Adapterパターン
まずはGofのAdapterパターンです。
実装方法はいくつか考えられますが、今回はコンポジションを使います。

```cpp
class PlayerAdapter final : public IFighter
{
public:
    // Playerと同様の引数で構築できるようにする
	template<typename... TArgs> 
	explicit PlayerAdapter(TArgs&&... args)
		: mPlayer(std::forward<TArgs>(args)...)
	{}

	void attack()  override { PlayerAttack(mPlayer); }
	void defence() override { PlayerDefence(mPlayer); }
private:
	Player mPlayer;
};

class EnemyAdapter final : public IFighter
{
public:
	template<typename... TArgs> 
	explicit EnemyAdapter(TArgs&&... args)
		: mEnemy(std::forward<TArgs>(args)...)
	{}

	void attack()  override { EnemyAttack(mEnemy); }
	void defence() override { EnemyDefence(mEnemy); }
private:
	Enemy mEnemy;
};
```

```cpp: テストコード
void test()
{
	PlayerAdapter player{};
	EnemyAdapter enemy{};

	fight(player); // Player's Attackなどと出力される
	fight(enemy);  // Enemy's Attackなどと出力される
}
```

___

さてここで第三のFighter、Animal君を追加しようとどうなるでしょうか？
```cpp
// 型と処理が追加された
class Animal {};
void AnimalAttack(Animal&) { Print("Animal's Attack"); }
void AnimalDefence(Animal&){ Print("Animal's Defence"); }
```

```diff_cpp
+ // Adapterクラスも用意
+ class AnimalAdapter final : public IFighter
+ {
+ public:
+     template<typename... TArgs>
+     explicit AnimalAdapter(TArgs&&... args)
+         : mAnimal(std::forward<TArgs>(args)...)
+     {}
+
+     void attack() override { AnimalAttack(mAnimal); }
+     void defence() override { AnimalDefence(mAnimal); }
+ private:
+     Animal mAnimal;
+ };
```
このくらいのコードが追加されます。
本質的には、
* attackをAnimalAttackにバイパス
* defenceをAnimalDefenceにバイパス

したいだけなのですが、それ以外の定型コードが多いです。

___

今度は、Playerに異なる攻撃方法を使わせたい場合はどうなるでしょう？
```cpp
// 新たな処理を追加
void PlayerAttack2(Player&) { Print("Player's Attack2"); }
```

```diff_cpp
+ class PlayerAdapter2 final : public IFighter
+ {
+ public:
+	  template<typename... TArgs>
+	  explicit PlayerAdapter2(TArgs&&... args)
+       : mPlayer(std::forward<TArgs>(args)...)
+	  {}
+
+     void attack()  override { PlayerAttack2(mPlayer); }
+     void defence() override { PlayerDefence(mPlayer); }
+ private:
+     Player mPlayer;
+ };
```
やっぱり、やりたいことに関係ないコードが多いですね。

PlayerAdapterが、
* Playerという具象クラスを、IFighterインターフェースに適合させる
* attackにはPlayerAttack関数を、defenceにはPlayerDefence関数を用いる

という風にいろんな知識を抱え込んでいるため、拡張が大変なのでした。
ということで、知識を分割して外に出してみましょう。

## Adapterパターン改

まずは、「attackにはPlayerAttack関数を、defenceにはPlayerDefence関数を用いる」という知識をPlayerAdapterから追い出しましょう。
ここでは静的Strategyパターンを用います。

```cpp: Adapterパターン改
// attack/defenceに何の処理を用いるかという知識を、TFightStrategyに委譲
template<typename TFightStrategy>
class PlayerAdapterRevised final : public IFighter
{
public:
	template<typename... TArgs>
	explicit PlayerAdapterRevised( TFightStrategy&& s, TArgs&&... args)
		: mPlayer(std::forward<TArgs>(args)...)
		, mStrategy(std::forward<TFightStrategy>(s))
	{}

	void attack()  override { mStrategy.attack(mPlayer); }
	void defence() override { mStrategy.defence(mPlayer); }
private:
	Player mPlayer;
	TFightStrategy mStrategy;
};

// TFightStrategyに与えるクラス
// Playerについて、attackにはPlayerAttack関数を、defenceにはPlayerDefence関数を用いるという知識を司る
class PlayerFightStrategy
{
public:
	void attack(Player& p)  { PlayerAttack(p); }
	void defence(Player& p) { PlayerDefence(p); }
};
```

```cpp
void test()
{
    // 推論されてPlayerAdapterRevised<PlayerFightStrategy>型になる
	PlayerAdapterRevised playerrev{ PlayerFightStrategy{} };

	fight(playerrev); 
}
```
※PlayerAdapterRevisedの初期化では[コンストラクタ引数の推論](https://cpprefjp.github.io/lang/cpp17/type_deduction_for_class_templates.html)を用いています。

これで、PlayerAttack2を使いたい場合はPlayerAttackStrategyと同様のクラスを増やすだけになりました。

## External Polymorphismパターン

さて、PlayerAdapterRevisedと同様にEnemyAdapterRevisedも作ろうと思いますが、よく見ると変えるべき部分はPlayer->Enemyの型しかありません。テンプレートにしてしまいましょう。

```cpp: External Polymorphismパターン
// PlayerやEnemyが入る部分をTObjテンプレート引数に
// アダプタとして最低限の責務だけが残された
template<typename TObj, typename TFightStrategy>
class FighterAdapter final : public IFighter
{
public:
	explicit FighterAdapter(TObj&& obj, TFightStrategy&& strategy)
		: mObj(std::forward<TObj>(obj))
		, mStrategy(std::forward<TFightStrategy>(strategy))
	{}

	void attack()  override { mStrategy.attack(mObj); }
	void defence() override { mStrategy.defence(mObj); }

private:
	TObj mObj;
	TFightStrategy mStrategy;
};

// TFightStrategyに与えるクラス
// Playerの分とEnemyの分をまとめたが、別に他のクラスに分けても問題ない
class FightStrategy
{
public:
	void attack(Player& p)  { PlayerAttack(p);  }
	void defence(Player& p) { PlayerDefence(p); }

    void attack(Enemy& e) { EnemyAttack(e);  }
    void defence(Enemy& e){ EnemyDefence(e); }
};
```

```cpp: テストコード
void test()
{
	FighterAdapter player{ Player{}, FightStrategy{} }; // FighterAdapter<Player,FightStrategy>に推論
	FighterAdapter enemy { Enemy{},  FightStrategy{} }; // FighterAdapter<Enemy,FightStrategy>に推論

	fight(player);
	fight(enemy);
}
```
もはや、AdapterクラスはPlayer/Enemyといった具象クラスの知識も、attack/defenceに対してどの関数を適用するかという知識も持たなくなりました。つまり、これらの拡張に対して全く無縁でいられる=これ以上手をいれる必要がなくなりました！
___
Adapterパターンの時にやったように、新たな型Animalを追加してみます。
```cpp
// 型と処理が追加された
class Animal {};
void AnimalAttack(Animal&) { Print("Animal's Attack"); }
void AnimalDefence(Animal&){ Print("Animal's Defence"); }
```

```diff_cpp
// Playerの分とEnemyの分とAnimalの分をまとめたが、別に他のクラスに分けても問題ない
class FightStrategy
{
public:
	void attack(Player& p)  { PlayerAttack(p);  }
	void defence(Player& p) { PlayerDefence(p); }

    void attack(Enemy& e) { EnemyAttack(e);  }
    void defence(Enemy& e){ EnemyDefence(e); }

+   void attack(Animal& a) { AnimalAttack(a); }
+   void defence(Animal& a){ AnimalDefence(a); }
}; 
```
コードの修正はこれだけです。さらに言えばAnimal向けの処理はFightStrategyではないクラスに書いてもいいので、元のFightStrategyを触らずに追加することもできます。

```cpp: テストコード
void test()
{
    FighterAdapter animal{ Animal{}, FightStrategy{} };

    fight(animal);
}
```
___

Playerに異なる攻撃方法を使わせたい場合もやってみましょう。
```cpp
// 新たな処理を追加
void PlayerAttack2(Player&) { Print("Player's Attack2"); }
```

```diff_cpp
+ // 新たなStrategyクラスを追加
+ class FightStrategy2
+ {
+ public:
+     void attack(Player& p)  { PlayerAttack2(p); }
+ 	  void defence(Player& p) { PlayerDefence(p); }
+ };
```
こちらも、もともとのAdapterパターンと比較して本質に関係ないコードの追加量がずいぶんと減りました。
```cpp: テストコード
void test()
{
 	FighterAdapter player2{ Player{}, FightStrategy2{} };

 	fight(player2); // Player's Attack2などと出力される
}
```

型・処理の追加それぞれに対してオープンな実装となりましたが、「Adapterパターン」「(静的)Strategyパターン」「テンプレート」といろいろなデザインパターンの集合体なので、なじみのない人には把握に時間がかかるコードでしょう。行き過ぎたDRYであるといわれるかもしれません。型・処理の追加が激しく行われて、通常のAdapterパターンではAdapterクラスのボイラープレートコードが爆増してしまう、というくらいのケースでのみ使用するのが良いかと思います。

※このパターンは[C++ソフトウェア設計 ―高品質設計の原則とデザインパターン](https://www.amazon.co.jp/C-%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E8%A8%AD%E8%A8%88-%E2%80%95%E9%AB%98%E5%93%81%E8%B3%AA%E8%A8%AD%E8%A8%88%E3%81%AE%E5%8E%9F%E5%89%87%E3%81%A8%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3-Klaus-Iglberger/dp/4814400454)にて「External Polymorphismパターン」として紹介されています。ただ、解説の導入の流れがしっくりこなかったため、個人的に納得いく導入を書き起こしてみた次第です。書籍によると、
* Adapterパターンは、既存のインターフェースに適合させるためのもの
* External Polymorphismパターンは、既存の一連の型を多態的に扱う目的で抽象化している

という目的の違いがある故別のパターンなのだと主張していますが、あんまり納得いきませんでした。（どっちも結局同じことでは・・・）

## 余談①：Concept

C++20以上が使えるなら、FighterAdapterのテンプレート引数TFightStrategyにConceptによる制約を付けてあげると少し親切でしょう。
```cpp
// TStrageyは、TObj&に適用できるattack関数とdefence関数を持つべしという制約
template<typename TStrategy,typename TObj>
concept FightStrategyConcept = requires(TStrategy strategy, TObj& obj)
{
	strategy.attack(obj);
	strategy.defence(obj);
};

template<typename TObj, FightStrategyConcept<TObj> TFightStrategy>
class FighterAdapter final : public IFighter
{
    // 中身は一緒
};
```

## 余談②：TFightStrategyの粒度
今回はTFightStrategyがattack/defence関数を持つものとして抽象化しましたが、attack/defenceをそれぞれバラバラに受けつける設計も考えられます。

```cpp
template<
	typename TObj, 
	std::invocable<TObj&> TAttack, 
	std::invocable<TObj&> TDefence
>
class FighterAdapter2 final : public IFighter
{
public:
	explicit FighterAdapter2(TObj&& obj,TAttack&& attack, TDefence&& defence )
		: mObj(std::forward<TObj>(obj))
		, mAttack(std::forward<TAttack>(attack))
		, mDefence(std::forward<TDefence>(defence))
	{}

	void attack()  override { mAttack(mObj); }
	void defence() override { mDefence(mObj); }

private:
	TObj mObj;
	TAttack mAttack;
	TDefence mDefence;
};
```

```cpp: テストコード
void test()
{
    // 利用者側で、Attack/Defenceの処理を好きなように組み立てられる
	FighterAdapter2 player { Player{},&PlayerAttack,  &PlayerDefence };
	FighterAdapter2 player2{ Player{},&PlayerAttack2, &PlayerDefence }; // 攻撃だけ違う処理に変えた

	fight(player);
	fight(player2);
}
```
柔軟性が増した一方で、TAttack/TDefenceが同じ変数を参照する場合にメモリが無駄になったり、その変数の寿命管理が複雑化したりします。
```cpp: テストコード
void test()
{
	struct State {} state;
	FighterAdapter2 stateful{
		Player{},
		[&state](Player&) { /* stateとplayer両方使った処理 */ },
		[&state](Player&) { /* stateとplayer両方使った処理 */ }
	};

    // このstatefulが外に持ち出されたとき、state変数の寿命の管理をTAttackとTDefenceどちらがしたらいいかわからないので、shared_ptrなどの機構が必要
    // TFightStrategyにまとめていれば、そのクラスだけが管理すればよかった
}
```

このあたりの議論は[C++における抽象化の手段比較](https://qiita.com/kobitnex/items/0f04bcd58fb8887a4ab7)もご参照ください。