開発をしていると、種類の増えるオブジェクトを扱う場面が必ず出てきます。
小規模であればnewで直接生成しても問題ないですが、規模が大きくなると密結合になり、変更に弱い実装となってしまいます。
そこで役に立つのがFactory Methodパターンです。
Factory Methodパターンを使用することで変更に強い生成の実装を可能にします。
本記事では、ゲーム開発を模したコードを使って Factory Methodパターンについてなるべくわかりやすく解説します。
前提知識
-
C++の基礎文法を理解している
Factory Methodパターンとは
Factory Methodパターンは、どの具体クラスを生成するかという判断をサブクラスに委譲するためのデザインパターンです。
- 新しい種類のオブジェクトを追加しても既存コードを触らずに済む
- テスト用のダミーオブジェクトを差し替えることができる
- 生成処理を整理して保守性を高められる
といったメリットがあります。
その反面で、 - クラス数が増えやすい
- 継承前提の設計になる
- 生成処理が単純な場合はオーバーエンジニアリング気味になる
といったデメリットも存在します。
Factory Methodパターンの用語と構成
Factory Methodパターンは以下の要素で構成されます。
-
Product
- 生成される
オブジェクトの抽象インターフェース - 概念だけを定義し、具体的な攻撃方法はサブクラスに任せる
- 生成される
-
ConcreteProduct
-
Productを継承し、実際の振る舞いを持つクラス -
ConcreteCreatorから生成される
-
-
Creator
- 生成処理を宣言するクラス
- どの具体クラスを生成するかは知らない
- 共通処理を持ち、生成だけを
サブクラスに委譲する
-
ConcreteCreator
-
Creatorを継承し、実際にどのConcreteProductを生成するかを決めるクラス - 生成する種類ごとにこのクラスを用意する
-
構造の図解
サンプルコード
ゲーム開発の敵を模したFactory Methodパターンのサンプルコード
//----------------------------------------------------------
//! @file Enemy.h
//! @brief 敵キャラクターの基底クラス定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include <iostream>
#include <memory>
//----------------------------------------------------------
//! @class Enemy
//! @brief 敵キャラクターの基底クラス
//! @note Factory MethodパターンのProductを担う抽象クラス
//----------------------------------------------------------
class Enemy {
public:
//----------------------------------------------------------
//! @brief 敵キャラクターの攻撃処理(純粋仮想関数)
//----------------------------------------------------------
virtual void attack() = 0;
//----------------------------------------------------------
//! @brief デストラクタ
//----------------------------------------------------------
virtual ~Enemy() = default;
};
//----------------------------------------------------------
//! @file Slime.h
//! @brief スライムの定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include "Enemy.h"
//----------------------------------------------------------
//! @class Slime
//! @brief スライムのクラス
//! @note Factory MethodパターンのConcreteProductを担う具体クラス
//----------------------------------------------------------
class Slime : public Enemy {
public:
//----------------------------------------------------------
// スライムの攻撃処理
//! @note Enemyクラスの純粋仮想関数をオーバーライド
//----------------------------------------------------------
void attack() override;
};
//----------------------------------------------------------
//! @file Slime.cpp
//! @brief スライムの実装
//! @author つきの
//----------------------------------------------------------
#include <iostream>
#include "Slime.h"
//----------------------------------------------------------
//! @brief スライムの攻撃処理
//----------------------------------------------------------
void Slime::attack() {
std::cout << "ぷるぷるしている!\n";
}
//----------------------------------------------------------
//! @file Goblin.h
//! @brief ゴブリンの定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include "Enemy.h"
//----------------------------------------------------------
//! @class Goblin
//! @brief ゴブリンのクラス
//! @note Factory MethodパターンのConcreteProductを担う具体クラス
//----------------------------------------------------------
class Goblin : public Enemy {
public:
//----------------------------------------------------------
// ゴブリンの攻撃処理
//! @note Enemyクラスの純粋仮想関数をオーバーライド
//----------------------------------------------------------
void attack() override;
};
//----------------------------------------------------------
//! @file Goblin.cpp
//! @brief ゴブリンの実装
//! @author つきの
//----------------------------------------------------------
#include <iostream>
#include "Goblin.h"
//----------------------------------------------------------
//! @brief ゴブリンの攻撃処理
//----------------------------------------------------------
void Goblin::attack() {
std::cout << "こん棒で攻撃!\n";
}
//----------------------------------------------------------
//! @file EnemyFactory.h
//! @brief 敵キャラクター生成の基底クラス定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include <memory>
#include "Enemy.h"
//----------------------------------------------------------
//! @class EnemyFactory
//! @brief 敵キャラクター生成の基底クラス
//! @note Factory MethodパターンのCreatorを担う抽象クラス
//----------------------------------------------------------
class EnemyFactory {
public:
//----------------------------------------------------------
//! @brief 敵キャラクター生成の純粋仮想関数
//! @return 生成された敵キャラクターのポインタ
//----------------------------------------------------------
virtual std::unique_ptr<Enemy> createEnemy() const = 0;
//----------------------------------------------------------
//! @brief デストラクタ
//----------------------------------------------------------
virtual ~EnemyFactory() = default;
};
//----------------------------------------------------------
//! @file SlimeFactory.h
//! @brief スライム生成クラス定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include "EnemyFactory.h"
//----------------------------------------------------------
//! @class SlimeFactory
//! @brief スライム生成クラス
//! @note Factory MethodパターンのConcreteCreatorを担う具体クラス
//----------------------------------------------------------
class SlimeFactory : public EnemyFactory {
public:
//----------------------------------------------------------
// スライム生成の実装
//! @return 生成されたスライムのポインタ
//! @note EnemyFactoryクラスの純粋仮想関数をオーバーライド
//----------------------------------------------------------
std::unique_ptr<Enemy> createEnemy() const override;
};
//----------------------------------------------------------
//! @file SlimeFactory.cpp
//! @brief スライム生成クラス実装
//! @author つきの
//----------------------------------------------------------
#include "SlimeFactory.h"
#include "Slime.h"
//----------------------------------------------------------
//! @brief スライム生成の実装
//----------------------------------------------------------
std::unique_ptr<Enemy> SlimeFactory::createEnemy() const{
return std::make_unique<Slime>();
}
//----------------------------------------------------------
//! @file GoblinFactory.h
//! @brief ゴブリン生成クラス定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include "EnemyFactory.h"
//----------------------------------------------------------
//! @class GoblinFactory
//! @brief ゴブリン生成クラス
//! @note Factory MethodパターンのConcreteCreatorを担う具体クラス
//----------------------------------------------------------
class GoblinFactory : public EnemyFactory {
public:
//----------------------------------------------------------
//! @brief ゴブリン生成の実装
//! @return 生成されたゴブリンのポインタ
//----------------------------------------------------------
std::unique_ptr<Enemy> createEnemy() const override;
};
//----------------------------------------------------------
//! @file GoblinFactory.cpp
//! @brief ゴブリン生成クラス実装
//! @author つきの
//----------------------------------------------------------
#include "GoblinFactory.h"
#include "Goblin.h"
//----------------------------------------------------------
//! @brief ゴブリン生成の実装
//----------------------------------------------------------
std::unique_ptr<Enemy> GoblinFactory::createEnemy() const {
return std::make_unique<Goblin>();
}
//----------------------------------------------------------
//! @file main.cpp
//! @brief Factory Methodパターンのサンプルコード
//! @author つきの
//----------------------------------------------------------
#include <iostream>
#include <memory>
#include "EnemyFactory.h"
#include "GoblinFactory.h"
#include "SlimeFactory.h"
//エントリポイント
int main() {
//----------------------------------------------------------
//! @brief 敵キャラクター生成用のファクトリーポインタ
//----------------------------------------------------------
std::unique_ptr<EnemyFactory> factory;
//----------------------------------------------------------
//スライムを出す
//----------------------------------------------------------
factory = std::make_unique<SlimeFactory>();
auto slime = factory->createEnemy();
slime->attack();
//----------------------------------------------------------
//ゴブリンを出す
//----------------------------------------------------------
factory = std::make_unique<GoblinFactory>();
auto goblin = factory->createEnemy();
goblin->attack();
//プログラムの終了
return 0;
}
ぷるぷるしている!
こん棒で攻撃!
クラス図
Product
Createrで生成されるクラスのインターフェースです。
本記事ではスライムやゴブリンの基底クラスとして、抽象の敵クラスを定義しています。
//----------------------------------------------------------
//! @file Enemy.h
//! @brief 敵キャラクターの基底クラス定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include <iostream>
#include <memory>
//----------------------------------------------------------
//! @class Enemy
//! @brief 敵キャラクターの基底クラス
//! @note Factory MethodパターンのProductを担う抽象クラス
//----------------------------------------------------------
class Enemy {
public:
//----------------------------------------------------------
//! @brief 敵キャラクターの攻撃処理(純粋仮想関数)
//----------------------------------------------------------
virtual void attack() = 0;
//----------------------------------------------------------
//! @brief デストラクタ
//----------------------------------------------------------
virtual ~Enemy() = default;
};
ConcreteProduct
実際に生成されるクラス(本記事ではSlimeやGoblin)です。
Product(本記事ではEnemy)を継承することで、Creatorの関数からProductのポインタとして生成されます。
//----------------------------------------------------------
//! @file Slime.h
//! @brief スライムの定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include "Enemy.h"
//----------------------------------------------------------
//! @class Slime
//! @brief スライムのクラス
//! @note Factory MethodパターンのConcreteProductを担う具体クラス
//----------------------------------------------------------
class Slime : public Enemy {
public:
//----------------------------------------------------------
// スライムの攻撃処理
//! @note Enemyクラスの純粋仮想関数をオーバーライド
//----------------------------------------------------------
void attack() override;
};
//----------------------------------------------------------
//! @file Slime.cpp
//! @brief スライムの実装
//! @author つきの
//----------------------------------------------------------
#include <iostream>
#include "Slime.h"
//----------------------------------------------------------
//! @brief スライムの攻撃処理
//----------------------------------------------------------
void Slime::attack() {
std::cout << "ぷるぷるしている!\n";
}
//----------------------------------------------------------
//! @file Goblin.h
//! @brief ゴブリンの定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include "Enemy.h"
//----------------------------------------------------------
//! @class Goblin
//! @brief ゴブリンのクラス
//! @note Factory MethodパターンのConcreteProductを担う具体クラス
//----------------------------------------------------------
class Goblin : public Enemy {
public:
//----------------------------------------------------------
// ゴブリンの攻撃処理
//! @note Enemyクラスの純粋仮想関数をオーバーライド
//----------------------------------------------------------
void attack() override;
};
//----------------------------------------------------------
//! @file Goblin.cpp
//! @brief ゴブリンの実装
//! @author つきの
//----------------------------------------------------------
#include <iostream>
#include "Goblin.h"
//----------------------------------------------------------
//! @brief ゴブリンの攻撃処理
//----------------------------------------------------------
void Goblin::attack() {
std::cout << "こん棒で攻撃!\n";
}
Creator
Productを生成するクラスのインターフェースです。
どのConcreteProduct(本記事ではSlimeやGoblin)を生成するかは知らず、 実際の生成処理は派生クラス/ConcreteCreatorに任せます。
Creatorは共通のインターフェースだけを提供し、
利用側はCreatorを通してProduct(本記事ではEnemy)を扱うことで、 具体的なクラスに依存しない柔軟な設計を実現できます。
//----------------------------------------------------------
//! @file EnemyFactory.h
//! @brief 敵キャラクター生成の基底クラス定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include <memory>
#include "Enemy.h"
//----------------------------------------------------------
//! @class EnemyFactory
//! @brief 敵キャラクター生成の基底クラス
//! @note Factory MethodパターンのCreatorを担う抽象クラス
//----------------------------------------------------------
class EnemyFactory {
public:
//----------------------------------------------------------
//! @brief 敵キャラクター生成の純粋仮想関数
//! @return 生成された敵キャラクターのポインタ
//----------------------------------------------------------
virtual std::unique_ptr<Enemy> createEnemy() const = 0;
//----------------------------------------------------------
//! @brief デストラクタ
//----------------------------------------------------------
virtual ~EnemyFactory() = default;
};
ConcreteCreator
Creatorを継承し、実際にどの ConcreteProductを生成するかを決めるクラスです。
createEnemy() のようなFactory Methodを実装し、SlimeやGoblinといった具体的なオブジェクトを生成します。
//----------------------------------------------------------
//! @file SlimeFactory.h
//! @brief スライム生成クラス定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include "EnemyFactory.h"
//----------------------------------------------------------
//! @class SlimeFactory
//! @brief スライム生成クラス
//! @note Factory MethodパターンのConcreteCreatorを担う具体クラス
//----------------------------------------------------------
class SlimeFactory : public EnemyFactory {
public:
//----------------------------------------------------------
// スライム生成の実装
//! @return 生成されたスライムのポインタ
//! @note EnemyFactoryクラスの純粋仮想関数をオーバーライド
//----------------------------------------------------------
std::unique_ptr<Enemy> createEnemy() const override;
};
//----------------------------------------------------------
//! @file SlimeFactory.cpp
//! @brief スライム生成クラス実装
//! @author つきの
//----------------------------------------------------------
#include "SlimeFactory.h"
#include "Slime.h"
//----------------------------------------------------------
//! @brief スライム生成の実装
//----------------------------------------------------------
std::unique_ptr<Enemy> SlimeFactory::createEnemy() const{
return std::make_unique<Slime>();
}
//----------------------------------------------------------
//! @file GoblinFactory.h
//! @brief ゴブリン生成クラス定義
//! @author つきの
//----------------------------------------------------------
#pragma once
#include "EnemyFactory.h"
//----------------------------------------------------------
//! @class GoblinFactory
//! @brief ゴブリン生成クラス
//! @note Factory MethodパターンのConcreteCreatorを担う具体クラス
//----------------------------------------------------------
class GoblinFactory : public EnemyFactory {
public:
//----------------------------------------------------------
//! @brief ゴブリン生成の実装
//! @return 生成されたゴブリンのポインタ
//----------------------------------------------------------
std::unique_ptr<Enemy> createEnemy() const override;
};
//----------------------------------------------------------
//! @file GoblinFactory.cpp
//! @brief ゴブリン生成クラス実装
//! @author つきの
//----------------------------------------------------------
#include "GoblinFactory.h"
#include "Goblin.h"
//----------------------------------------------------------
//! @brief ゴブリン生成の実装
//----------------------------------------------------------
std::unique_ptr<Enemy> GoblinFactory::createEnemy() const {
return std::make_unique<Goblin>();
}
利用側はConcreteCreatorを差し替えるだけで動作が変わるため、モックテストが容易であり、変更にも強いです。
//----------------------------------------------------------
//! @file main.cpp
//! @brief Factory Methodパターンのサンプルコード
//! @author つきの
//----------------------------------------------------------
#include <iostream>
#include <memory>
#include "EnemyFactory.h"
#include "GoblinFactory.h"
#include "SlimeFactory.h"
//エントリポイント
int main() {
//----------------------------------------------------------
//! @brief 敵キャラクター生成用のファクトリーポインタ
//----------------------------------------------------------
std::unique_ptr<EnemyFactory> factory;
//----------------------------------------------------------
//スライムを出す
//----------------------------------------------------------
factory = std::make_unique<SlimeFactory>();
auto slime = factory->createEnemy();
slime->attack();
//----------------------------------------------------------
//ゴブリンを出す
//----------------------------------------------------------
factory = std::make_unique<GoblinFactory>();
auto goblin = factory->createEnemy();
goblin->attack();
//プログラムの終了
return 0;
}
総括
-
Factory Methodパターンを使用することで、テストが容易になり、変更に強い設計となる -
ConcreteProductの分だけConcreteCreatorが必要なので、クラスの数が多くなってしまうというデメリットもある