GoFデザインパターン - 抽象ファクトリ編
GoFコレクションにおける抽象ファクトリパターン実践ガイド:概念、使いどころ、C++の実装例、注意点と出典を実用的に解説します
こんにちは!パン君です。
今回は GoF(Gang of Four)のデザインパターンの抽象ファクトリ編になります。
サンプルコード(C++)、作り方、使うタイミング、注意点などを含めて実用的に解説します。
はじめに
抽象ファクトリとは 関連する、あるいは依存する オブジェクト群を 具体クラスを明示せずに生成するインターフェースを提供するパターンです。
一時情報として Wikipedia の定義を引用します。
"関連または依存するオブジェクトのファミリーを、具体的なクラスを指定せずに作成するためのインターフェース。"
https://en.wikipedia.org/wiki/Abstract_factory_pattern
"オブジェクト作成のためのインターフェースを定義し実装することで、オブジェクト作成を別のオブジェクトにカプセル化する。代わりにファクトリオブジェクトにオブジェクト作成を委譲する。"
(出典: https://en.wikipedia.org/wiki/Abstract_factory_pattern)
上記の通り、このデザインパターンはクライアントは抽象的なインターフェースだけを扱い、どの具象製品が生成されるかを知らずに済みます。
実装を切り替えるだけで一貫したテーマをまとめて差し替えられます。
いつ使うべきか
採用を検討する状況の例としていくつかあげます。
- UIテーマやプラットフォーム依存のUI部品群を切り替えたいとき
- 複数の互換性を保った製品群を一貫して生成したいとき(例:
ButtonとCheckboxのペアを切り替えたいとき) - ランタイムまたは構成で実装を切り替える必要があるとき
代替手段としては下記の例があります。
- 単純なら
Simple Factory(条件分岐で生成)で十分か確認する - 単一製品の切り替えなら
Factory Methodの方が適切な場合がある - 依存注入(DI)と組み合わせるとテスト性が向上する
構造(要素)
AbstractFactory(抽象ファクトリ): 製品群を生成するファクトリのインターフェースConcreteFactory(具象ファクトリ): 抽象ファクトリを実装し、関連する具象製品を生成AbstractProductA,AbstractProductB(抽象製品): 製品の抽象インターフェースConcreteProductA1,ConcreteProductB1(具象製品): 製品の具象実装Client(クライアント): 抽象ファクトリを使って製品を取得し、抽象インターフェースで操作する
C++ 実装例
以下は典型的な Button / Checkbox の製品群を持つ抽象ファクトリの簡潔な C++ サンプルです。
// AbstractFactory C++
#include <memory>
#include <iostream>
#include <string>
// 抽象製品 A: Button
struct Button {
virtual ~Button() = default;
virtual std::string render() const = 0;
};
// 抽象製品 B: Checkbox
struct Checkbox {
virtual ~Checkbox() = default;
virtual std::string render() const = 0;
};
// 具象製品群 1: Windows 系
struct WindowsButton : Button {
std::string render() const override { return "WindowsButton"; }
};
struct WindowsCheckbox : Checkbox {
std::string render() const override { return "WindowsCheckbox"; }
};
// 具象製品群 2: Mac 系
struct MacButton : Button {
std::string render() const override { return "MacButton"; }
};
struct MacCheckbox : Checkbox {
std::string render() const override { return "MacCheckbox"; }
};
// 抽象ファクトリ
struct GUIFactory {
virtual ~GUIFactory() = default;
virtual std::unique_ptr<Button> createButton() const = 0;
virtual std::unique_ptr<Checkbox> createCheckbox() const = 0;
};
// 具象ファクトリ: Windows
struct WindowsFactory : GUIFactory {
std::unique_ptr<Button> createButton() const override {
return std::make_unique<WindowsButton>();
}
std::unique_ptr<Checkbox> createCheckbox() const override {
return std::make_unique<WindowsCheckbox>();
}
};
// 具象ファクトリ: Mac
struct MacFactory : GUIFactory {
std::unique_ptr<Button> createButton() const override {
return std::make_unique<MacButton>();
}
std::unique_ptr<Checkbox> createCheckbox() const override {
return std::make_unique<MacCheckbox>();
}
};
// Client は GUIFactory に依存する(抽象に依存)
void renderUI(const GUIFactory& factory) {
auto btn = factory.createButton();
auto chk = factory.createCheckbox();
std::cout << "Render: " << btn->render() << ", " << chk->render() << "\n";
}
int main() {
WindowsFactory wf;
MacFactory mf;
renderUI(wf); // Render: WindowsButton, WindowsCheckbox
renderUI(mf); // Render: MacButton, MacCheckbox
return 0;
}サンプルのポイントは下記です。
renderUIは具象クラスを知らず、GUIFactoryが提供する製品をそのまま使う- 新しいテーマ(製品族)を追加しても
renderUIのコードを変更する必要がない
メリット / デメリット
メリット
- 製品群を一貫して生成でき、実装の切替が容易になる
- クライアントは具体クラスから分離され、可搬性・テスト性が向上する
- 新しい製品族を導入する際、クライアントコードの改修は最小限で済む
デメリット
- 新しい「製品種類(AbstractProduct)」を追加すると全ての
ConcreteFactoryに実装追加が必要になり、変更コストが高くなる - 初期設計で抽象化の粒度を間違えると「過剰設計」になり、コードの複雑化を招く
- 実装切替時にシリアライズや API、データフォーマットとの互換性に影響が出る可能性が高い
まとめ
抽象ファクトリは 関連する製品群 を一貫して切り替える場面で強力なパターンです。
採用前に 追加される製品の範囲 テスト戦略 API/シリアライズ への影響 を評価してください。
依存注入と組み合わせるとテスト性・可換性が向上します。
小さく始めて必要になれば抽象化を拡張する、という方針が安全です。
参考・出典を記します
en.wikipedia.orgAbstract factory pattern - Wikipedia
それでは、次回は別の GoF パターンについても解説していきます。
この記事で提示したコードは学習目的に簡潔化しています。
実プロダクションで採用する際は要件に合わせて十分に検討してください
以上、パン君でした!
コメントを読み込み中...