PHP デザインパターン学習:Abstract Factory パターン - 関連するオブジェクト群をファミリーとして生成する
はじめに
Abstract Factory(抽象ファクトリ)パターンは、GoF デザインパターンの中でも特に重要な生成パターンの一つです。このパターンを使うことで、関連する一連のオブジェクト群を、具体的な実装クラスを指定せずに生成することができます。
本記事では、Abstract Factory パターンの概念、Factory Method パターンとの違い、PHP での実装例、実際のプロジェクトでの活用シーン、そしてメリット・デメリットについて解説します。
本記事のサンプルコードの完全版は GitHub リポジトリで公開しています
Abstract Factory パターンとは何か?
Abstract Factory パターンは、関連または依存するオブジェクト群を、その具体的なクラスを指定せずに生成するためのインターフェースを提供するデザインパターンです。
ここで言う「具体的なクラスを指定せずに」とは、クライアントコードのコンストラクタやメソッドの引数の型に、実装クラスではなくインターフェースを指定することで実現されています。例えば:
DatabaseClient のサンプル
public function __construct(DatabaseFactory $factory)
{
$this->connection = $factory->createConnection();
$this->queryBuilder = $factory->createQueryBuilder();
}
この例では、DatabaseClient は具体的なファクトリクラス(MySqlFactory や PostgreSqlFactory)ではなく、DatabaseFactory インターフェースに依存しています。
このパターンの核心は以下の点にあります:
- 複数の関連製品:単一のオブジェクトではなく、複数の関連するオブジェクト群(製品ファミリー)を生成する
製品ファミリーとは、互いに協調して動作する関連オブジェクト群のことです。例えば:
- MySQL ファミリー:MySqlConnection, MySqlQueryBuilder
- PostgreSQL ファミリー:PostgreSqlConnection, PostgreSqlQueryBuilder
「生成」とは、DatabaseClient のコンストラクタ内での以下のような処理を指します:
$this->connection = $factory->createConnection();
$this->queryBuilder = $factory->createQueryBuilder();
-
抽象インターフェース:クライアントコードが具体的な実装クラスを知る必要がなく、インターフェースを通じて操作できる
-
一貫性の保証:生成されるオブジェクト群が互いに矛盾なく協調して動作することを保証する
このパターンは「工場の工場」と表現されることもあります。つまり、様々な種類の関連オブジェクトを生成する工場自体を抽象化し、具体的な工場の実装を切り替えることで、生成するオブジェクト群全体を簡単に切り替えられるのです。
Factory Method パターンとの違い
Abstract Factory パターンと Factory Method パターンは混同されやすいですが、その焦点は大きく異なります:
特徴 | Factory Method | Abstract Factory |
---|---|---|
生成対象 | 単一のオブジェクト | 関連するオブジェクト群 |
実装方法 | メソッドによるオブジェクト生成 | 複数の生成メソッドを持つファクトリオブジェクト |
拡張方法 | サブクラス化による拡張 | 新しいファクトリクラスの追加 |
適用場面 | 単一タイプのオブジェクト生成を抽象化 | 一連の関連オブジェクトの生成を抽象化 |
簡単に言えば、Factory Method は「どのようにオブジェクトを生成するか」を抽象化するのに対し、Abstract Factory は「どの関連オブジェクト群を生成するか」を抽象化します。
実際のコードで見ると、Factory Method は単一のメソッドによってオブジェクトを生成するのに対し、Abstract Factory はインターフェースとして複数の生成メソッドを持ち、関連する一連のオブジェクトを生成します。
PHP での実装例と解説
PHP で Abstract Factory パターンを実装した例を見てみましょう。ここでは、データベース操作に関するコンポーネント(Connection, QueryBuilder)をファミリーとして生成する Abstract Factory を実装します。
まずはインターフェースを定義します:
// データベースコネクションのインターフェース
interface Connection
{
public function connect(): bool;
public function disconnect(): bool;
public function getConnectionInfo(): string;
}
// クエリビルダーのインターフェース
interface QueryBuilder
{
public function select(string $table, array $fields): string;
public function insert(string $table, array $data): string;
public function update(string $table, array $data, string $condition): string;
public function delete(string $table, string $condition): string;
}
// データベースファクトリのインターフェース
interface DatabaseFactory
{
public function createConnection(): Connection;
public function createQueryBuilder(): QueryBuilder;
}
次に、MySQL 向けの具体的な実装を作成します:
// MySQLファクトリの実装
class MySqlFactory implements DatabaseFactory
{
public function createConnection(): Connection
{
return new MySqlConnection();
}
public function createQueryBuilder(): QueryBuilder
{
return new MySqlQueryBuilder();
}
}
// MySQL接続の実装
class MySqlConnection implements Connection
{
// 実装詳細省略...
}
// MySQLクエリビルダーの実装
class MySqlQueryBuilder implements QueryBuilder
{
// 実装詳細省略...
}
同様に、PostgreSQL 向けの実装も作成します:
// PostgreSQLファクトリの実装
class PostgreSqlFactory implements DatabaseFactory
{
public function createConnection(): Connection
{
return new PostgreSqlConnection();
}
public function createQueryBuilder(): QueryBuilder
{
return new PostgreSqlQueryBuilder();
}
}
// PostgreSQL関連の具体的な実装クラス
// 実装詳細省略...
最後に、クライアントコードでの使用例を示します:
// クライアントコード
class DatabaseClient
{
private Connection $connection;
private QueryBuilder $queryBuilder;
public function __construct(DatabaseFactory $factory)
{
$this->connection = $factory->createConnection();
$this->queryBuilder = $factory->createQueryBuilder();
}
// メソッド実装省略...
}
// 設定に応じて適切なファクトリを使用
$dbType = 'mysql'; // または 'postgres'
if ($dbType === 'mysql') {
$factory = new MySqlFactory();
} else {
$factory = new PostgreSqlFactory();
}
// クライアントコードはどのデータベースを使うか知らなくても良い
$dbClient = new DatabaseClient($factory);
// 例えば、PostgreSQLに切り替えるのも簡単
$postgresClient = new DatabaseClient(new PostgreSqlFactory());
この実装例から分かるポイント:
- DatabaseFactory インターフェースが、関連するコンポーネント(Connection と QueryBuilder)を生成するためのメソッドを定義
- 具体的なファクトリ(MySqlFactory と PostgreSqlFactory)がそれぞれのデータベース向けの一貫したコンポーネントセットを提供
- クライアントコード(DatabaseClient)は具体的な実装に依存せず、抽象インターフェースを通じて操作
メリットとデメリット
メリット
-
一貫性の保証: 同じファクトリによって生成されたオブジェクト群は、互いに矛盾なく協調して動作することが保証されます。
-
実装の分離: クライアントコードは具体的な実装クラスを知る必要がなく、抽象インターフェースを通じて操作できるため、結合度が低下します。
-
コード変更の局所化: 新しい製品ファミリー(例:新しいデータベースタイプ)を追加する場合、既存のコードを変更せずに新しいファクトリクラスを追加するだけで済みます。
-
テストの容易さ: モックファクトリを使用して、テスト環境で特定の動作をするオブジェクト群を簡単に生成できます。
デメリット
-
コード量の増加: 多くのインターフェースと実装クラスが必要になるため、コード量が増加します。
-
複雑性の増加: シンプルなケースでは過剰な抽象化になる可能性があり、コードが複雑になることがあります。
-
柔軟性の制限: 製品ファミリーに新しい種類の製品を追加するのが難しく、ファクトリインターフェースの変更が必要になります。
「柔軟性の制限」については少し詳しく説明しましょう。例えば、MySqlFactory に「createStoredProcedure()」のような独自メソッドを追加したい場合、このメソッドは DatabaseFactory インターフェースに含まれていないため、すべての具象ファクトリクラスでも実装する必要が生じ、「インターフェース分離の原則」というルールに反します。
- パフォーマンスへの影響: 抽象化のレイヤーが増えることで、わずかながらパフォーマンスへの影響がある場合があります。
まとめ
Abstract Factory パターンは、関連するオブジェクト群をファミリーとして扱い、その具体的な実装を抽象化することで、システムの柔軟性と拡張性を高めるデザインパターンです。
特に、複数のプラットフォームやバックエンドシステムをサポートする必要があるアプリケーションや、テーマ切り替え機能を持つ UI フレームワークなど、関連するコンポーネント群を一貫して扱う必要がある場合に非常に有効です。
ただし、シンプルなケースでは過剰な抽象化になる可能性があるため、実際のプロジェクトで使用する際は、複雑さとメリットのバランスを考慮することが重要です。
Factory Method パターンとの違いを理解し、適切な場面で使い分けることで、より柔軟で保守性の高いコードを書くことができるでしょう。
Discussion