Factory Method と Abstract Factory について Java
1. 概要
Factory Method と Abstract Factory はどちらも creational pattern に属し、具体的なクラスを直接 new する必要なくオブジェクトを作成するのに役立ちます。しかし、この2つのパターンは異なる問題を解決し、初心者には非常に混同しやすいものです。
この記事では、PaymentMethod の例(Factory Method)とテーマに基づく UI 作成システム(Abstract Factory)の例を通じて、その違いを見ることができます。読み終わると、シンプルな factory だけで十分な場合と、「工場を生産する工場」全体が必要な場合を区別できるようになります。
2. Factory Method とは?
Factory Method はオブジェクトを作成するためのメソッドを定義しますが、どの具体的なクラスを作成するかはサブクラスが決定します。簡単に言えば、create() メソッドを持つインターフェースがあり、各サブクラスが異なる実装を返すようにそれをオーバーライドします。
2.1. 解決すべき問題
複数の支払い方法を持つ決済システムがあると仮定します:CreditCardPayment、MomoPayment、BankTransferPayment。クライアントコードが直接 new する場合:
public void processOrder(Order order, String type) {
PaymentMethod payment;
if (type.equals("CREDIT_CARD")) {
payment = new CreditCardPayment();
} else if (type.equals("MOMO")) {
payment = new MomoPayment();
} else {
payment = new BankTransferPayment();
}
payment.pay(order.getAmount());
}
支払い方法が追加されるたびに、それを使うすべての場所でこの if-else を修正する必要があります。これはまさに Factory Method が解決する code smell です。
2.2. Factory Method の適用
public interface PaymentMethod {
void pay(BigDecimal amount);
}
public class CreditCardPayment implements PaymentMethod {
@Override
public void pay(BigDecimal amount) {
System.out.println("クレジットカードで " + amount + " を支払っています");
}
}
public class MomoPayment implements PaymentMethod {
@Override
public void pay(BigDecimal amount) {
System.out.println("Momo で " + amount + " を決済しました");
}
}
説明:PaymentMethod は共通インターフェースで、各実装は自身の決済ロジックだけに専念します。これが factory が作成する「製品」(product)です。
次に、Factory Method を含む抽象クラスがあります:
public abstract class PaymentProcessor {
// Factory Method - サブクラスがどのインスタンスを作成するかを決定します
protected abstract PaymentMethod createPaymentMethod();
public void processOrder(Order order) {
PaymentMethod payment = createPaymentMethod();
payment.pay(order.getAmount());
}
}
public class CreditCardProcessor extends PaymentProcessor {
@Override
protected PaymentMethod createPaymentMethod() {
return new CreditCardPayment();
}
}
public class MomoProcessor extends PaymentProcessor {
@Override
protected PaymentMethod createPaymentMethod() {
return new MomoPayment();
}
}
createPaymentMethod()は Factory Method です:これは抽象メソッドで、各サブクラスが必要なPaymentMethodの正しいタイプを返すようにオーバーライドします。processOrder()は共通「テンプレート」で、どのタイプの支払いが作成されているかの詳細を知る必要がありません。
クライアントは以下のように使用します:
PaymentProcessor processor = new MomoProcessor();
processor.processOrder(new Order(new BigDecimal("150000")));
Momo で 150000 を決済しました
2.3. Factory Method をテストする
class PaymentProcessorTest {
@Test
void shouldCreateCorrectPaymentMethod() {
PaymentProcessor processor = new CreditCardProcessor();
PaymentMethod method = processor.createPaymentMethod();
assertThat(method).isInstanceOf(CreditCardPayment.class);
}
}
注意:
createPaymentMethod()(protected)をテストできるようにするには、テストクラスをPaymentProcessorと同じパッケージに配置するか、外部からテストする必要がある場合はメソッドをpublicに変更してください。
3. Abstract Factory とは?
Abstract Factory は、各オブジェクトの具体的なクラスを指定する必要なく、**関連するオブジェクトのグループ(ファミリー)**を作成するためのインターフェースを提供します。1つのタイプの製品のみを作成する Factory Method とは異なり、Abstract Factory は「一緒に行く」必要がある複数の製品を作成します。
3.1. 解決すべき問題
アプリケーションの UI テーマを作成していると仮定します:「Light」テーマと「Dark」テーマ。各テーマは独自の Button と Checkbox が必要で、これら2つは互いにスタイルが同期していなければなりません。LightButton と DarkCheckbox を混ぜることはできません。
public interface Button {
void render();
}
public interface Checkbox {
void render();
}
public class LightButton implements Button {
@Override
public void render() {
System.out.println("白背景に黒いテキストのボタンを描画");
}
}
public class DarkButton implements Button {
@Override
public void render() {
System.out.println("黒背景に白いテキストのボタンを描画");
}
}
public class LightCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("薄いグレーの境界線でチェックボックスを描画");
}
}
public class DarkCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("白い境界線でチェックボックスを描画");
}
}
3.2. Abstract Factory の定義
public interface UiComponentFactory {
Button createButton();
Checkbox createCheckbox();
}
public class LightThemeFactory implements UiComponentFactory {
@Override
public Button createButton() {
return new LightButton();
}
@Override
public Checkbox createCheckbox() {
return new LightCheckbox();
}
}
public class DarkThemeFactory implements UiComponentFactory {
@Override
public Button createButton() {
return new DarkButton();
}
@Override
public Checkbox createCheckbox() {
return new DarkCheckbox();
}
}
UiComponentFactory は製品ファミリー(Button + Checkbox)全体を定義するインターフェースです。各実装(LightThemeFactory、DarkThemeFactory)は、作成された製品が常にテーマの点で一貫していることを保証します。
3.3. クライアントで Abstract Factory を使用する
public class ThemedApplication {
private final UiComponentFactory factory;
public ThemedApplication(UiComponentFactory factory) {
this.factory = factory;
}
public void renderUi() {
Button button = factory.createButton();
Checkbox checkbox = factory.createCheckbox();
button.render();
checkbox.render();
}
}
ThemedApplication app = new ThemedApplication(new DarkThemeFactory());
app.renderUi();
黒背景に白いテキストのボタンを描画
白い境界線でチェックボックスを描画
ThemedApplication は、どの具体的なクラスが作成されているのか知る必要も必要ありません。これは単に UiComponentFactory インターフェースに依存しているため、テーマを変更するにはインジェクトされた factory を変更するだけで済みます。
3.4. Abstract Factory をテストする
class ThemedApplicationTest {
@Test
void shouldCreateConsistentComponentsForDarkTheme() {
UiComponentFactory factory = new DarkThemeFactory();
Button button = factory.createButton();
Checkbox checkbox = factory.createCheckbox();
assertThat(button).isInstanceOf(DarkButton.class);
assertThat(checkbox).isInstanceOf(DarkCheckbox.class);
}
}
このテストは Abstract Factory の最も重要な特性を確認します:同じ factory から作成された製品は常に同じ「ファミリー」に属し、Light と Dark が混在することはありません。
4. Factory Method と Abstract Factory の比較
| 評価基準 | Factory Method | Abstract Factory |
|---|---|---|
| 製品タイプの数 | 単一の製品タイプ(PaymentMethod) |
関連する複数の製品タイプ(Button、Checkbox) |
| メカニズム | サブクラスでメソッドをオーバーライド | 複数の factory method を 1 つのインターフェースに構成 |
| 関係性 | 継承(Inheritance) | 通常は composition を使用(factory を inject) |
| 目的 | クライアントを単一の object の具体的なクラスの知識から解放 | 複数の関連 object 間の一貫性を確保 |
簡単に覚える方法:Abstract Factory は通常、複数の Factory Method のセットで実装されます。上の例では、createButton() と createCheckbox() が UiComponentFactory にある2つの Factory Method です。
4.1. Factory Method をいつ使うか
- 単一タイプの object のみを作成する必要がありますが、作成ロジックは複雑であるか、コンテキストに応じて変わります。
- クラス階層を扱っており、サブクラスが使用する child オブジェクトのタイプを自分で決定させたい場合(template method pattern はこのパターンとよく組み合わせられます)。
4.2. Abstract Factory をいつ使うか
- 複数の関連 object を作成する必要があり、それらが相互に互換性があることを確認する必要があります(同じテーマ、同じデータベースベンダー、同じプラットフォームなど)。
- システムは新しい「製品ファミリー」を追加できる必要があります(例えば
HighContrastThemeFactoryを追加)が、クライアントコードを変更したくない場合。
注意: 製品が 1 つのタイプのみの場合は Abstract Factory を使用しないでください。余分なインターフェース層が作成されます。単純な Factory Method で十分です。
5. Spring Boot との統合
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
Spring では、Spring container がすでに factory の役割を果たしているため、GoF パターンに厳密に従って Factory Method/Abstract Factory を手動で作成することはめったにありません。ただし、その概念をしっかり理解することで、bean をより良く設計できます。
@Configuration
public class PaymentFactoryConfig {
@Bean
public Map<String, PaymentMethod> paymentMethods() {
return Map.of(
"CREDIT_CARD", new CreditCardPayment(),
"MOMO", new MomoPayment()
);
}
}
@Service
public class PaymentService {
private final Map<String, PaymentMethod> paymentMethods;
public PaymentService(Map<String, PaymentMethod> paymentMethods) {
this.paymentMethods = paymentMethods;
}
public void pay(String type, BigDecimal amount) {
PaymentMethod method = paymentMethods.get(type);
if (method == null) {
throw new IllegalArgumentException("サポートされていない支払いタイプ: " + type);
}
method.pay(amount);
}
}
ここで、Spring container は真の「工場」の役割を果たします:PaymentMethod bean を事前に作成し、クライアントはキーで検索するだけです。これは Spring 環境での Factory Method の実用的な変形です。各支払いタイプ用に個別の XxxProcessor クラスを書く必要がなくなります。
6. よくある間違い
- Factory Method と static factory method(
Optional.of()、List.of()など)を混同する。静的 factory method はコンストラクターより簡潔なオブジェクト作成方法であり、GoF パターンのポリモーフィズムとは関係ありません。 - 製品が 1 つのみの場合の Abstract Factory の乱用:
Checkboxが伴わないButtonのみの場合、Abstract Factory は過度な設計です。 - Factory に多くのビジネスロジックを認識させる:factory はオブジェクト作成のみを処理し、注文処理やバリデーションなどのロジックを埋め込むべきではありません。
- Abstract Factory の一貫性確保を忘れる:クライアントが factory A から独立して
Buttonを選択し、factory B からCheckboxを選択できるようにした場合、パターンの主な目的を破壊してしまいます。
7. 結論
Factory Method は、単一の object の作成を subclass に委譲する必要がある場合に適しており、Open/Closed 原則に従ったコードの拡張を容易にします。Abstract Factory はさらに先に進みます:関連するオブジェクトのグループが常に一貫して作成されることを保証し、互換性のないコンポーネントが混在するのを防ぎます。
Spring Boot での実践では、通常 GoF 構造に従って手動で書くのではなく、DI container を factory として活用します。しかし、これら2つのパターンの本質をしっかり理解することで、ライブラリコード(DocumentBuilderFactory、JDBC の ConnectionFactory など)をより良く理解でき、拡張モジュールの設計も向上させることができます。