「UIUI [ユーアイ]User Interface。ユーザーとシステムの間で情報をやり取りする接点や画面のテーマ(ライト/ダーク)を切り替える際、関連するボタンやウィンドウの部品をまとめて差し替えたい」「OSOS [オーエス]Operating System。基本ソフトウェア(Windows/macOSmacOS [マックオーエス]Appleが開発するMac用のオペレーティングシステム)ごとに対応したUIコンポーネントを矛盾なく生成したい」といった課題に直面したことはありませんか?
本記事では、互いに関連するオブジェクト群をまとめて生成するための「Abstract Factory(アブストラクトファクトリー)パターン」について、JavaJava [ジャバ]オブジェクト指向プログラミング言語の実装例を交えてわかりやすく解説します。
記事のポイント
- Abstract Factoryパターンは、互いに関連・依存するオブジェクト群を、具体的なクラスを指定することなく生成するためのデザインパターンです。
- 「製品の群(ファミリー)」を一括して切り替えることができるため、設定の矛盾(WindowsのボタンとmacOSのチェックボックスが混ざるなど)を防げます。
- Factory Methodパターンが「1つの製品の生成」に注目するのに対し、Abstract Factoryパターンは「関連する複数の製品群の生成」に注目します。
- 新しい製品群(新しいテーマなど)を追加するのは簡単ですが、新しい種類の製品(新しい部品など)を追加するのは難しいという特徴があります。
Abstract Factory(アブストラクトファクトリー)パターンとは?
Abstract Factory(アブストラクトファクトリー) パターンは、GoF(Gang of Four)によって定義された23のデザインパターンのうち、「生成に関するパターン」の1つです。
直訳すると「抽象的な工場」となります。このパターンでは、具体的な実装(どのクラスをインスタンス化するか)をクライアント(利用側)から隠蔽し、インターフェース(抽象クラス)を通してのみオブジェクト群を生成・利用します。
Factory Methodパターンとの違い
どちらもオブジェクト生成をカプセル化するパターンですが、焦点を当てている範囲が異なります。
- Factory Methodパターン: 主に1種類の製品を生成するためのメソッドを定義し、その具体的な生成方法をサブクラスなどに委ねます。
- Abstract Factoryパターン: 関連する複数種類の製品を同じファミリーとして生成するためのインターフェースを提供します。Abstract Factoryの各生成メソッドは、Factory Methodとして実装される場合があります。そのため、両者は密接に関連するパターンです。
Abstract Factoryパターンの構造と登場人物
このパターンには、大きく分けて以下の役割を持つクラスが登場します。
- AbstractFactory(抽象的な工場) 関連する製品群を生成するためのAPIAPI [エーピーアイ]Application Programming Interface。異なるソフトウェア間の接続口(メソッド群)を定義するインターフェース(または抽象クラス)です。
- ConcreteFactory(具体的な工場)
AbstractFactoryを実装し、特定のバリエーション(テーマやOSなど)に対応する具体的な製品群を生成します。 - AbstractProduct(抽象的な製品) 各製品のインターフェース(または抽象クラス)を定義します。
- ConcreteProduct(具体的な製品)
AbstractProductを実装した具体的な製品クラスです。 - Client(依頼者)
AbstractFactoryとAbstractProductのインターフェースのみを使って、具体的なクラスには依存せずに処理を行います。
Abstract FactoryパターンのJava実装例
ここでは、GUIGUI [ジーユーアイ / グーイ]Graphical User Interface。グラフィカルな操作画面アプリケーションにおける「ライトテーマ」と「ダークテーマ」のUIコンポーネント(ボタンとチェックボックス)をまとめて生成する例をJavaで実装します。 ※簡潔にするため複数のクラスをまとめて掲載しています。実際には各publicクラスを対応するファイルに分けてください。
1. AbstractProduct(抽象的な製品)の定義
まずは、生成される各コンポーネント(製品)の共通インターフェースを定義します。
// ボタンのインターフェースpublic interface Button { void paint();}
// チェックボックスのインターフェースpublic interface Checkbox { void paint();}2. ConcreteProduct(具体的な製品)の実装
次に、各テーマ(バリエーション)に対応した具体的なコンポーネントを実装します。
// ライトテーマ用のボタンpublic class LightButton implements Button { @Override public void paint() { System.out.println("ライトテーマのボタンを描画します。"); }}
// ダークテーマ用のボタンpublic class DarkButton implements Button { @Override public void paint() { System.out.println("ダークテーマのボタンを描画します。"); }}
// ライトテーマ用のチェックボックスpublic class LightCheckbox implements Checkbox { @Override public void paint() { System.out.println("ライトテーマのチェックボックスを描画します。"); }}
// ダークテーマ用のチェックボックスpublic class DarkCheckbox implements Checkbox { @Override public void paint() { System.out.println("ダークテーマのチェックボックスを描画します。"); }}3. AbstractFactory(抽象的な工場)の定義
関連する部品(ここではボタンとチェックボックス)を生成するためのインターフェースを定義します。
// UI部品を生成する抽象ファクトリーpublic interface GUIFactory { Button createButton(); Checkbox createCheckbox();}4. ConcreteFactory(具体的な工場)の実装
テーマごとに具体的な製品を生成するファクトリーを実装します。
// ライトテーマ用のコンポーネント群を生成するファクトリーpublic class LightThemeFactory implements GUIFactory { @Override public Button createButton() { return new LightButton(); }
@Override public Checkbox createCheckbox() { return new LightCheckbox(); }}
// ダークテーマ用のコンポーネント群を生成するファクトリーpublic class DarkThemeFactory implements GUIFactory { @Override public Button createButton() { return new DarkButton(); }
@Override public Checkbox createCheckbox() { return new DarkCheckbox(); }}5. Client(依頼者)クラスと実行例
クライアント側は、渡されたファクトリーの種類を気にすることなく、インターフェース経由で一貫性のある製品群を利用できます。
public class Application { private Button button; private Checkbox checkbox;
// コンストラクタでファクトリーを受け取り、必要な部品を生成 public Application(GUIFactory factory) { button = factory.createButton(); checkbox = factory.createCheckbox(); }
public void paintUI() { button.paint(); checkbox.paint(); }}
public class Main { public static void main(String[] args) { // 設定や環境に応じてファクトリーを切り替える String theme = "dark"; // 例えば設定ファイルなどから読み込む GUIFactory factory;
if (theme.equals("dark")) { factory = new DarkThemeFactory(); } else { factory = new LightThemeFactory(); }
// クライアント側は具体的なクラスに依存せずUIを描画できる Application app = new Application(factory); app.paintUI(); }}実行結果のイメージ:
ダークテーマのボタンを描画します。ダークテーマのチェックボックスを描画します。このように、ファクトリーを差し替えるだけで、生成される関連オブジェクトの群(ファミリー)全体を安全に切り替えることができます。ライト用のボタンとダーク用のチェックボックスなど、異なるファミリーの製品が混在するのを防ぎやすくなるのが大きなメリットです。
Abstract Factoryパターンを採用するメリット・デメリット
メリット
- 製品群の一貫性を保ちやすい:関連するオブジェクトを同じファミリーから生成する仕組みにできるため、異なるファミリーのオブジェクトが混在する不整合を防ぎやすくなります。
- クライアントと具体的なクラスを疎結合にできる:クライアントは抽象ファクトリーと抽象製品に依存するため、具体的な製品クラスへの依存を減らし、製品ファミリーの切り替えによるクライアント側への影響を抑えられます。これは、具体的な実装への依存を減らし、拡張しやすい設計につながります。
- 新しいファミリー(製品群)の追加が容易:例えば新しく「マテリアルデザインテーマ」を追加したい場合、新しい
ConcreteFactoryと関連するConcreteProduct群を追加することで対応しやすく、既存のクライアント処理への影響を抑えながら追加できます。
デメリット・注意点
- 新しい種類の製品(部品)を追加するのが困難:例えば、後から新しく「テキストボックス」という部品(AbstractProduct)を追加しようとすると、
AbstractFactoryインターフェースを変更する必要があり、その影響が全てのConcreteFactoryクラスに及んでしまいます。 - コードが複雑になりがち:多くのインターフェースとクラスを定義する必要があるため、シンプルなアプリケーションには過剰設計(オーバーエンジニアリング)になる可能性があります。
まとめ:関連するオブジェクト群を一括して切り替えよう
Abstract Factory パターンは、関連する複数のオブジェクト群を、矛盾なく一貫して生成するための強力なデザインパターンです。
「テーマの切り替え」「対応OSごとの部品の切り替え」「データベースの種類(MySQLMySQL [マイエスキューエル]オープンソースのリレーショナルデータベース, PostgreSQLPostgreSQL [ポストグレスキューエル]高機能オープンソースのリレーショナルデータベースなど)ごとの一連のアクセス部品の切り替え※」など、「一連の関連する部品をまとめて差し替えたい」 という要件がある場合に非常に適しています。
※補足:データベースアクセスの切り替えについて、例えばJDBCの
DriverManagerなどは、利用側の文脈によっては Abstract Factory というよりも Factory Method パターンに近い実装となることも多くあります。どちらのパターンに該当するかは、「関連する複数の製品群を作っているか(Abstract Factory)」「単一の製品を作っているか(Factory Method)」によって判断されます。
システムが将来どの方向に拡張されるか(部品のバリエーションが増えるのか、部品の種類そのものが増えるのか)を見極めたうえで、適切に導入を検討してみてください。
【参考情報】
以上で本記事の解説を終わります。
よいITライフを!
人気記事
- 1
- 2
- 3
- 4
- 5
思考の整理というテーマを、実務ですぐに使える具体的な「技術」として落とし込んだ一冊です。