開発をしていると、「関連するオブジェクトをまとめて生成したい」という場面が必ず出てきます。
例えば
- UIのテーマが変わると、ボタン・テキスト・ウィンドウなど複数の部品を一括で切り替えたい
- プラットフォームが異なると、対応する API 群をセットで生成したい
このように、複数のオブジェクトがセットで切り替わる状況では、個々のオブジェクトを直接newしていると、変更のたびにコードのあちこちを修正する必要があり、密結合で保守性の低い設計になってしまいます。
前回紹介したFactory Methodパターンでは、複数の関連オブジェクトをまとめて切り替えるといった要求には対応しきれません。
そこで登場するのがAbstract Factoryパターンです。
Abstract Factoryパターンを使用することで、関連するオブジェクト群を、統一されたインターフェースでまとめて生成することが可能になります。
本記事では、アプリケーション開発のUI描画を模したコードを使って Abstract Factoryパターンについてなるべくわかりやすく解説します。
前提知識
-
C++の基礎文法を理解している
Abstract Factoryパターンとは
Abstract Factoryパターンは、関連する複数のオブジェクトを、統一されたインターフェースを通してまとめて生成するためのデザインパターンです。
- 関連する
オブジェクト群をセットとして統一的に生成できる - 利用側は
具象クラスを意識せずに済み、依存関係を減らせる -
オブジェクト群の整合性を保ちやすい
といったメリットがあります。
その反面で、 - クラス数が多くなりやすい
- 構造が複雑になりやすく、小規模な用途では過剰設計になりがち
- 新しい
オブジェクト群を追加する場合、抽象インターフェースの変更が必要になる
といったデメリットも存在します。
Abstract Factoryパターンの用語と構成
Abstract Factoryパターンは以下の要素で構成されます。
-
AbstractProduct/抽象製品
- 生成物(
Product)の共通インターフェース定義 -
ConcreteProductはこれを継承することで、差し替えの際に整合性が保てる
- 生成物(
-
ConcreteProduct/具体製品
-
AbstractProductを実装したクラス -
Factoryから実際に生成されるもの
-
-
AbstractFactory/抽象ファクトリ
-
Product群をまとめて生成するためのインターフェース定義 - どの
Productを生成するかは派生クラス(ConcreteFactory)が決める
-
-
ConcreteFactory/具体ファクトリ
-
AbstractFactoryを実装したクラス - 特定の
Product群を生成する - テーマ・プラットフォーム・設定などに応じて複数用意される
-
構造の図解
サンプルコード
テーマごとに異なるUIのセットを生成するコード
//-------------------------------------------------------------
//! @file Button.h
//! @brief 抽象的なボタンのインターフェース定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
//-------------------------------------------------------------
//! @class Button
//! @brief 抽象的なボタンのインターフェース
//! @note Abstract FactoryパターンのAbstractProductの役割を担う
//-------------------------------------------------------------
class Button {
public:
//-------------------------------------------------------------
//! @brief ボタンを描画する純粋仮想関数
//! @note 派生クラスで実装をする
//-------------------------------------------------------------
virtual void draw() = 0;
//-------------------------------------------------------------
//! @brief 仮想デストラクタ
//-------------------------------------------------------------
virtual ~Button() = default;
};
//-------------------------------------------------------------
//! @file Text.h
//! @brief 抽象的なテキストのインターフェース定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
//-------------------------------------------------------------
//! @class Text
//! @brief 抽象的なテキストのインターフェース
//! @note Abstract FactoryパターンのAbstractProductの役割を担う
//-------------------------------------------------------------
class Text {
public:
//-------------------------------------------------------------
//! @brief テキストを描画する純粋仮想関数
//! @note 派生クラスで実装をする
//-------------------------------------------------------------
virtual void draw() = 0;
//-------------------------------------------------------------
//! @brief 仮想デストラクタ
//-------------------------------------------------------------
virtual ~Text() = default;
};
//-------------------------------------------------------------
//! @file DarkButton.h
//! @brief Darkテーマ用のボタンの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "Button.h"
//-------------------------------------------------------------
//! @class DarkButton
//! @brief Darkテーマ用のボタン
//! @note Abstract FactoryパターンのConcreteProductの役割を担う
//-------------------------------------------------------------
class DarkButton : public Button {
public:
//-------------------------------------------------------------
// ボタンを描画する
//-------------------------------------------------------------
void draw()override;
};
//-------------------------------------------------------------
//! @file DarkButton.cpp
//! @brief Darkテーマ用のボタンの実装
//! @author つきの
//-------------------------------------------------------------
#include "DarkButton.h"
#include <iostream>
//-------------------------------------------------------------
//! @brief ボタンを描画する
//-------------------------------------------------------------
void DarkButton::draw() {
std::cout << "Darkテーマのボタンを描画\n";
}
//-------------------------------------------------------------
//! @file LightButton.h
//! @brief Lightテーマ用のボタンの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "Button.h"
//-------------------------------------------------------------
//! @class LightButton
//! @brief Lightテーマ用のボタン
//! @note Abstract FactoryパターンのConcreteProductの役割を担う
//-------------------------------------------------------------
class LightButton : public Button {
public:
//-------------------------------------------------------------
// ボタンを描画する
//-------------------------------------------------------------
void draw() override;
};
//-------------------------------------------------------------
//! @file LightButton.cpp
//! @brief Lightテーマ用のボタンの実装
//! @author つきの
//-------------------------------------------------------------
#include "LightButton.h"
#include <iostream>
//-------------------------------------------------------------
//! @brief ボタンを描画する
//-------------------------------------------------------------
void LightButton::draw() {
std::cout << "Lightテーマのボタンを描画\n";
}
//-------------------------------------------------------------
//! @file DarkText.h
//! @brief Darkテーマ用のテキストの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "Text.h"
//-------------------------------------------------------------
//! @class DarkText
//! @brief Darkテーマ用のテキスト
//! @note Abstract FactoryパターンのConcreteProductの役割を担う
//-------------------------------------------------------------
class DarkText : public Text {
public:
//-------------------------------------------------------------
// テキストを描画する
//-------------------------------------------------------------
void draw() override;
};
//-------------------------------------------------------------
//! @file DarkText.cpp
//! @brief Darkテーマ用のテキストの実装
//! @author つきの
//-------------------------------------------------------------
#include "DarkText.h"
#include <iostream>
//-------------------------------------------------------------
//!@brief テキストを描画する
//-------------------------------------------------------------
void DarkText::draw() {
std::cout << "Darkテーマのテキストを描画\n";
}
//-------------------------------------------------------------
//! @file LightText.h
//! @brief Lightテーマ用のテキストの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "Text.h"
//-------------------------------------------------------------
//! @class LightText
//! @brief Lightテーマ用のテキスト
//! @note Abstract FactoryパターンのConcreteProductの役割を担う
//-------------------------------------------------------------
class LightText : public Text {
public:
//-------------------------------------------------------------
// テキストを描画する
//-------------------------------------------------------------
void draw() override;
};
//-------------------------------------------------------------
//! @file LightText.cpp
//! @brief Lightテーマ用のテキストの実装
//! @author つきの
//-------------------------------------------------------------
#include "LightText.h"
#include <iostream>
//-------------------------------------------------------------
//! @brief テキストを描画する
//-------------------------------------------------------------
void LightText::draw() {
std::cout << "Lightテーマのテキストを描画\n";
}
//-------------------------------------------------------------
//! @file UIFactory.h
//! @brief 抽象的なUIファクトリのインターフェース定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include <memory>
// 前方宣言
class Button;
class Text;
//-------------------------------------------------------------
//! @class UIFactory
//! @brief 抽象的なUIファクトリのインターフェース
//! @note Abstract FactoryパターンのAbstractFactoryの役割を担う
//-------------------------------------------------------------
class UIFactory {
public:
//-------------------------------------------------------------
//! @brief ボタンを生成する純粋仮想関数
//-------------------------------------------------------------
virtual std::unique_ptr<Button> createButton() const = 0;
//-------------------------------------------------------------
//! @brief テキストを生成する純粋仮想関数
//-------------------------------------------------------------
virtual std::unique_ptr<Text> createText() const = 0;
//-------------------------------------------------------------
//! @brief 仮想デストラクタ
//-------------------------------------------------------------
virtual ~UIFactory() = default;
};
//-------------------------------------------------------------
//! @file DarkThemeFactory.h
//! @brief Darkテーマ用のUIファクトリの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "UIFactory.h"
//-------------------------------------------------------------
//! @class DarkThemeFactory
//! @brief Darkテーマ用のUIファクトリ
//! @note Abstract FactoryパターンのConcreteFactoryの役割を担う
//-------------------------------------------------------------
class DarkThemeFactory : public UIFactory {
public:
//-------------------------------------------------------------
// Darkテーマ用のボタンとテキストを生成する
//! @note UIFactoryの純粋仮想関数をオーバーライドして実装
//-------------------------------------------------------------
std::unique_ptr<Button> createButton() const override;
//-------------------------------------------------------------
// Darkテーマ用のテキストを生成する
//! @note UIFactoryの純粋仮想関数をオーバーライドして実装
//-------------------------------------------------------------
std::unique_ptr<Text> createText() const override;
};
//-------------------------------------------------------------
//! @file DarkThemeFactory.cpp
//! @brief Darkテーマ用のUIファクトリの実装
//! @author つきの
//-------------------------------------------------------------
#include "DarkThemeFactory.h"
#include "DarkButton.h"
#include "DarkText.h"
//-------------------------------------------------------------
//! @brief Darkテーマ用のボタンとテキストを生成する
//-------------------------------------------------------------
std::unique_ptr<Button> DarkThemeFactory::createButton() const {
return std::make_unique<DarkButton>();
}
//-------------------------------------------------------------
//! @brief Darkテーマ用のテキストを生成する
//-------------------------------------------------------------
std::unique_ptr<Text> DarkThemeFactory::createText() const {
return std::make_unique<DarkText>();
}
//-------------------------------------------------------------
//! @file LightThemeFactory.h
//! @brief Lightテーマ用のUIファクトリの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "UIFactory.h"
//-------------------------------------------------------------
//! @class LightThemeFactory
//! @brief Lightテーマ用のUIファクトリ
//! @note Abstract FactoryパターンのConcreteFactoryの役割を担う
//-------------------------------------------------------------
class LightThemeFactory : public UIFactory {
public:
//-------------------------------------------------------------
// Lightテーマ用のボタンとテキストを生成する
//! @note UIFactoryの純粋仮想関数をオーバーライドして実装
//-------------------------------------------------------------
std::unique_ptr<Button> createButton() const override;
//-------------------------------------------------------------
// Lightテーマ用のテキストを生成する
//! @note UIFactoryの純粋仮想関数をオーバーライドして実装
//-------------------------------------------------------------
std::unique_ptr<Text> createText() const override;
};
//-------------------------------------------------------------
//! @file LightThemeFactory.cpp
//! @brief Lightテーマ用のUIファクトリの実装
//! @author つきの
//-------------------------------------------------------------
#include "LightThemeFactory.h"
#include "LightButton.h"
#include "LightText.h"
//-------------------------------------------------------------
//! @brief Lightテーマ用のボタンとテキストを生成する
//-------------------------------------------------------------
std::unique_ptr<Button> LightThemeFactory::createButton() const {
return std::make_unique<LightButton>();
}
//-------------------------------------------------------------
//! @brief Lightテーマ用のテキストを生成する
//-------------------------------------------------------------
std::unique_ptr<Text> LightThemeFactory::createText() const {
return std::make_unique<LightText>();
}
//-------------------------------------------------------------
//! @file main.cpp
//! @brief UI生成を模したAbstract Factoryパターンのサンプルコード
//! @author つきの
//-------------------------------------------------------------
#include "Button.h"
#include "Text.h"
#include "UIFactory.h"
#include "LightThemeFactory.h"
#include "DarkThemeFactory.h"
//エントリポイント
int main() {
// UIファクトリのポインタを生成
std::unique_ptr<UIFactory> factory;
//-------------------------------------------------------------
// LightテーマのUIを生成
//-------------------------------------------------------------
factory = std::make_unique<LightThemeFactory>(); // LightテーマのUIファクトリを生成
auto btn1 = factory->createButton(); // Lightテーマのボタンを生成
auto txt1 = factory->createText(); // Lightテーマのテキストを生成
btn1->draw(); // ボタンを描画
txt1->draw(); // テキストを描画
//-------------------------------------------------------------
// DarkテーマのUIを生成
//-------------------------------------------------------------
factory = std::make_unique<DarkThemeFactory>(); // DarkテーマのUIファクトリを生成
auto btn2 = factory->createButton(); // Darkテーマのボタンを生成
auto txt2 = factory->createText(); // Darkテーマのテキストを生成
btn2->draw(); // ボタンを描画
txt2->draw(); // テキストを描画
//プログラムの終了
return 0;
}
Lightテーマのボタンを描画
Lightテーマのテキストを描画
Darkテーマのボタンを描画
Darkテーマのテキストを描画
クラス図
AbstractProduct/抽象製品
ConcreteProductが継承する共通インターフェースを定義します。
これを基底クラスに持つことで、Factoryが生成するProduct群としての整合性を保つことが可能になります。
//-------------------------------------------------------------
//! @file Button.h
//! @brief 抽象的なボタンのインターフェース定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
//-------------------------------------------------------------
//! @class Button
//! @brief 抽象的なボタンのインターフェース
//! @note Abstract FactoryパターンのAbstractProductの役割を担う
//-------------------------------------------------------------
class Button {
public:
//-------------------------------------------------------------
//! @brief ボタンを描画する純粋仮想関数
//! @note 派生クラスで実装をする
//-------------------------------------------------------------
virtual void draw() = 0;
//-------------------------------------------------------------
//! @brief 仮想デストラクタ
//-------------------------------------------------------------
virtual ~Button() = default;
};
//-------------------------------------------------------------
//! @file Text.h
//! @brief 抽象的なテキストのインターフェース定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
//-------------------------------------------------------------
//! @class Text
//! @brief 抽象的なテキストのインターフェース
//! @note Abstract FactoryパターンのAbstractProductの役割を担う
//-------------------------------------------------------------
class Text {
public:
//-------------------------------------------------------------
//! @brief テキストを描画する純粋仮想関数
//! @note 派生クラスで実装をする
//-------------------------------------------------------------
virtual void draw() = 0;
//-------------------------------------------------------------
//! @brief 仮想デストラクタ
//-------------------------------------------------------------
virtual ~Text() = default;
};
ConcreteProduct/具体製品
AbstractProductで定義されたインターフェースを実際に実装したクラスです。
テーマやプラットフォームごとの具体的な振る舞い(本記事ではDarkテーマとLightテーマ)を提供します。
//-------------------------------------------------------------
//! @file DarkButton.h
//! @brief Darkテーマ用のボタンの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "Button.h"
//-------------------------------------------------------------
//! @class DarkButton
//! @brief Darkテーマ用のボタン
//! @note Abstract FactoryパターンのConcreteProductの役割を担う
//-------------------------------------------------------------
class DarkButton : public Button {
public:
//-------------------------------------------------------------
// ボタンを描画する
//-------------------------------------------------------------
void draw()override;
};
//-------------------------------------------------------------
//! @file DarkText.h
//! @brief Darkテーマ用のテキストの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "Text.h"
//-------------------------------------------------------------
//! @class DarkText
//! @brief Darkテーマ用のテキスト
//! @note Abstract FactoryパターンのConcreteProductの役割を担う
//-------------------------------------------------------------
class DarkText : public Text {
public:
//-------------------------------------------------------------
// テキストを描画する
//-------------------------------------------------------------
void draw() override;
};
//-------------------------------------------------------------
//! @file LightButton.h
//! @brief Lightテーマ用のボタンの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "Button.h"
//-------------------------------------------------------------
//! @class LightButton
//! @brief Lightテーマ用のボタン
//! @note Abstract FactoryパターンのConcreteProductの役割を担う
//-------------------------------------------------------------
class LightButton : public Button {
public:
//-------------------------------------------------------------
// ボタンを描画する
//-------------------------------------------------------------
void draw() override;
};
//-------------------------------------------------------------
//! @file LightText.h
//! @brief Lightテーマ用のテキストの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "Text.h"
//-------------------------------------------------------------
//! @class LightText
//! @brief Lightテーマ用のテキスト
//! @note Abstract FactoryパターンのConcreteProductの役割を担う
//-------------------------------------------------------------
class LightText : public Text {
public:
//-------------------------------------------------------------
// テキストを描画する
//-------------------------------------------------------------
void draw() override;
};
AbstractFactory/抽象ファクトリ
複数のProductをまとめて生成するためのインターフェースを定義するクラスです。
Factory Methodが単一のオブジェクト生成を扱うのに対し、Abstract Factoryは関連する複数のオブジェクトをセットとして生成することに特化しています。
//-------------------------------------------------------------
//! @file UIFactory.h
//! @brief 抽象的なUIファクトリのインターフェース定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include <memory>
// 前方宣言
class Button;
class Text;
//-------------------------------------------------------------
//! @class UIFactory
//! @brief 抽象的なUIファクトリのインターフェース
//! @note Abstract FactoryパターンのAbstractFactoryの役割を担う
//-------------------------------------------------------------
class UIFactory {
public:
//-------------------------------------------------------------
//! @brief ボタンを生成する純粋仮想関数
//-------------------------------------------------------------
virtual std::unique_ptr<Button> createButton() const = 0;
//-------------------------------------------------------------
//! @brief テキストを生成する純粋仮想関数
//-------------------------------------------------------------
virtual std::unique_ptr<Text> createText() const = 0;
//-------------------------------------------------------------
//! @brief 仮想デストラクタ
//-------------------------------------------------------------
virtual ~UIFactory() = default;
};
生成関数の戻り値をAbstractProductにすることで、中身の差し替えを容易に行えます。
//-------------------------------------------------------------
//! @brief ボタンを生成する純粋仮想関数
//-------------------------------------------------------------
virtual std::unique_ptr<Button> createButton() const = 0;
//-------------------------------------------------------------
//! @brief テキストを生成する純粋仮想関数
//-------------------------------------------------------------
virtual std::unique_ptr<Text> createText() const = 0;
ConcreteFactory/具体ファクトリ
AbstractFactoryで定義されたインターフェースを実装し、特定のProduct群をまとめて生成するクラスです。
テーマ・プラットフォーム・設定などに応じて複数の ConcreteFactoryが存在し、それぞれが整合性の取れた製品の組み合わせを提供することで、差し替えの容易性や変更への強さを実現します。
//-------------------------------------------------------------
//! @file LightThemeFactory.h
//! @brief Lightテーマ用のUIファクトリの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "UIFactory.h"
//-------------------------------------------------------------
//! @class LightThemeFactory
//! @brief Lightテーマ用のUIファクトリ
//! @note Abstract FactoryパターンのConcreteFactoryの役割を担う
//-------------------------------------------------------------
class LightThemeFactory : public UIFactory {
public:
//-------------------------------------------------------------
// Lightテーマ用のボタンとテキストを生成する
//! @note UIFactoryの純粋仮想関数をオーバーライドして実装
//-------------------------------------------------------------
std::unique_ptr<Button> createButton() const override;
//-------------------------------------------------------------
// Lightテーマ用のテキストを生成する
//! @note UIFactoryの純粋仮想関数をオーバーライドして実装
//-------------------------------------------------------------
std::unique_ptr<Text> createText() const override;
};
//-------------------------------------------------------------
//! @file LightThemeFactory.cpp
//! @brief Lightテーマ用のUIファクトリの実装
//! @author つきの
//-------------------------------------------------------------
#include "LightThemeFactory.h"
#include "LightButton.h"
#include "LightText.h"
//-------------------------------------------------------------
//! @brief Lightテーマ用のボタンとテキストを生成する
//-------------------------------------------------------------
std::unique_ptr<Button> LightThemeFactory::createButton() const {
return std::make_unique<LightButton>();
}
//-------------------------------------------------------------
//! @brief Lightテーマ用のテキストを生成する
//-------------------------------------------------------------
std::unique_ptr<Text> LightThemeFactory::createText() const {
return std::make_unique<LightText>();
}
//-------------------------------------------------------------
//! @file DarkThemeFactory.h
//! @brief Darkテーマ用のUIファクトリの定義
//! @author つきの
//-------------------------------------------------------------
#pragma once
#include "UIFactory.h"
//-------------------------------------------------------------
//! @class DarkThemeFactory
//! @brief Darkテーマ用のUIファクトリ
//! @note Abstract FactoryパターンのConcreteFactoryの役割を担う
//-------------------------------------------------------------
class DarkThemeFactory : public UIFactory {
public:
//-------------------------------------------------------------
// Darkテーマ用のボタンとテキストを生成する
//! @note UIFactoryの純粋仮想関数をオーバーライドして実装
//-------------------------------------------------------------
std::unique_ptr<Button> createButton() const override;
//-------------------------------------------------------------
// Darkテーマ用のテキストを生成する
//! @note UIFactoryの純粋仮想関数をオーバーライドして実装
//-------------------------------------------------------------
std::unique_ptr<Text> createText() const override;
};
//-------------------------------------------------------------
//! @file DarkThemeFactory.cpp
//! @brief Darkテーマ用のUIファクトリの実装
//! @author つきの
//-------------------------------------------------------------
#include "DarkThemeFactory.h"
#include "DarkButton.h"
#include "DarkText.h"
//-------------------------------------------------------------
//! @brief Darkテーマ用のボタンとテキストを生成する
//-------------------------------------------------------------
std::unique_ptr<Button> DarkThemeFactory::createButton() const {
return std::make_unique<DarkButton>();
}
//-------------------------------------------------------------
//! @brief Darkテーマ用のテキストを生成する
//-------------------------------------------------------------
std::unique_ptr<Text> DarkThemeFactory::createText() const {
return std::make_unique<DarkText>();
}
#総括
-
Abstract Factoryパターンを使用することで、変更に強い形でまとまったオブジェクトの生成を可能にすることが出来る - テーマやプラットフォームの数だけ
ConcreteFactoryやConcreteProductが増えるので、クラスやファイル数が増えやすいというデメリットも存在している -
AbstractFactory/抽象ファクトリが定義した生成関数の戻り値をAbstractProduct/抽象製品にすることで、変更やテストに強い形を実現している