アーキテクチャ

ファクトリパターンとは?Factory Method・Abstract Factoryの違いとJava・Python実装例

ファクトリパターンとは?Factory Method・Abstract Factoryの違いとJava・Python実装例

ファクトリパターン(Factoryパターン)は、オブジェクトを作る処理を専用のクラスやメソッドに集め、呼び出し側が具体的なクラス名を知らなくても目的のオブジェクトを受け取れるようにする設計手法です。ただ「ファクトリパターン」という呼び名は、GoFのFactory MethodとAbstract Factory、GoFに含まれないSimple Factory、Javaでおなじみの静的ファクトリメソッドをまとめて指すことが多く、ここを混ぜたまま読むと解説記事ごとに話が食い違って見えます。この記事では4つの違いを先に整理し、実行確認したJava・Pythonのコード、JDKやSpringに組み込まれた実例、使わない方がいい場面まで順に説明します。

まとめ:ファクトリパターンの要点

  • 定義:生成処理(new)を呼び出し側から切り離し、1か所に集める設計。呼び出し側はインタフェースだけに依存する。
  • 4つの区別:条件分岐で作り分けるSimple Factory(GoF外のイディオム)、生成をサブクラスに任せるFactory Method、関連する部品一式を差し替えるAbstract Factory、コンストラクタ代わりの静的ファクトリメソッド。
  • 使いどころ:作る型が実行時の条件で変わる、生成手順が長い、型が今後増える、テストで偽物に差し替えたい、のいずれかがあるとき。
  • 使わない方がいい場面:作る型が1つで増える予定もなく、生成が単純で、生成の集約やテスト用の差し替えも不要なとき。この場合、ファクトリを挟んでもクラスと間接参照が増えるだけになる。
  • 身近な実例:JavaのList.of()やCalendar.getInstance()、SpringのBeanFactory、Pythonのdict.fromkeys()はいずれもファクトリ。

通知処理を例に、生成処理を呼び出し側から切り離す理由を見てみます。

ファクトリパターンの定義と解決する問題

通知クラスの生成分散による修正漏れ

通知機能を例にします。メール通知しか無かった頃に、画面・バッチ・APIの各所で new EmailNotifier(address) と書いていたとします。ここにSMS通知が加わると、呼び出し箇所をすべて探して if を足すことになり、1か所の直し漏れがそのまま不具合になります。問題の根は「どのクラスを作るか」という判断が呼び出し側に散らばっていることです。

ファクトリパターンはこの判断を1か所に集めます。呼び出し側は Notifier というインタフェースだけを扱い、どの実装を作るかはファクトリが決めます。SMSが加わっても直すのはファクトリだけで、呼び出し側は変わりません。呼び出し側を変更から守るという意味で「開放閉鎖の原則」(拡張に開き、修正に閉じる)に沿う形です(ファクトリ自体の修正が残る点は後述します)。実装をインタフェース越しに扱えるのはポリモーフィズム(多態性)のおかげです。

「factory」「ファクトリクラス」という語の範囲

英語の factory は「工場」で、プログラミングでは「オブジェクトを作って返すもの」を広く指します。オブジェクトを返す関数ならファクトリ関数、そのためのクラスならファクトリクラス(Factoryクラス)、メソッドならファクトリメソッドと呼ばれます。

検索で紛らわしいのが、同じ名前を持つ別物です。LaravelやRuby on Railsの「モデルファクトリ」(Factory Bot等)はテスト用のダミーデータを作る仕組みで、名前の由来は同じでも本記事の設計パターンとは目的が違います。Azure Data Factoryはデータ連携サービスの製品名で、設計パターンとは無関係です。

Simple Factory・Factory Method・Abstract Factoryの違い

先に一覧で違いを示し、そのあと1つずつ説明します。

名称 GoF 作り分けの手段 作るもの 向く場面
Simple Factory 含まれない 条件分岐(switch等) 1種類の製品 型が数個で分岐が単純
Factory Method 生成パターン 継承(サブクラス) 1種類の製品 手順は共通で部品だけ違う
Abstract Factory 生成パターン ファクトリ自体の差し替え 関連する製品一式 テーマ・DB種別の切替
静的ファクトリメソッド 含まれない クラスのstaticメソッド 戻り値型に適合するインスタンス コンストラクタの代替

GoF本『Design Patterns』(1994年、Gamma・Helm・Johnson・Vlissidesの4名)が定義した23パターンのうち、生成に関するパターンはAbstract Factory・Builder・Factory Method・Prototype・Singletonの5つです。「ファクトリパターン」という名前のパターンはGoFには無く、上の4つを総称する俗称として使われています。

Simple Factory:GoFに含まれない定番イディオム

条件に応じて switch や辞書で実装を選び、インスタンスを返すだけの一番素朴な形です。『Head First デザインパターン』第4章でも、Simple FactoryはFactory MethodとAbstract Factoryに入る前の導入として扱われ、正式なデザインパターンとは区別されています。本記事のJava例にあるNotifierFactoryは、この形のファクトリクラスです。型が数個で分岐も単純なうちは、これで十分です。

Factory Method:サブクラスによる生成処理の差し替え

GoFの定義は「オブジェクト生成のインタフェースを定め、どのクラスを生成するかはサブクラスに決めさせる」です。代表的な実装では、親クラスが処理の手順を持ち、途中で呼ぶ生成メソッドをサブクラスでオーバーライドします。生成メソッドは抽象メソッドにも、既定の生成処理を持つメソッドにもできます。CSV出力とTSV出力のように、流れは同じで使う部品だけが違う場合に向きます。分岐文ではなく継承で作り分ける点がSimple Factoryとの違いです。

Abstract Factory:関連する部品一式の差し替え

GoFの定義は「関連し合う、または依存し合うオブジェクト群を、具体的なクラスを指定せずに生成するインタフェースを提供する」です。ライトテーマ用のボタンとダイアログ、ダークテーマ用のボタンとダイアログのように、組み合わせを崩してはいけない部品の一式を作るときに使います。ファクトリを差し替えれば一式がまとめて入れ替わり、同じ具象ファクトリから部品を取る限りテーマは混在しません。ただし、掲載例の型定義だけでは、異なるファクトリから取得した部品の混在を禁止できません。代償として、製品の種類(例:チェックボックス)を1つ足すとファクトリのインタフェースとすべての実装を直すことになります。

静的ファクトリメソッド:コンストラクタの代わりの名前付き生成

『Effective Java 第3版』の項目1「コンストラクタの代わりにstaticファクトリメソッドを検討する」で広まった書き方です。JDKの List.of・Integer.valueOf・Instant.from・Calendar.getInstance のように、意図を表す名前を付けたstaticメソッドがインスタンスを返します。GoFのFactory Methodとは名前が似ているだけで別物で、継承も抽象メソッドも使いません。利点は、名前で意図を示せること、毎回新しいインスタンスを作らずに済むこと、戻り値の型をサブタイプにできることです。

Javaでのファクトリパターン実装例

3つの形を1ファイルにまとめたコードです。Java 17以降のsealedインタフェースとswitch式を使っており、Temurin 25で javac と java を実行して動作を確かめています。

import java.util.List;

// 1. Simple Factory
sealed interface Notifier permits EmailNotifier, SmsNotifier {
    void send(String message);
}

record EmailNotifier(String address) implements Notifier {
    public void send(String message) {
        System.out.println("mail to " + address + ": " + message);
    }
}

record SmsNotifier(String phone) implements Notifier {
    public void send(String message) {
        System.out.println("sms to " + phone + ": " + message);
    }
}

final class NotifierFactory {
    private NotifierFactory() {}

    static Notifier create(String channel, String to) {
        return switch (channel) {
            case "email" -> new EmailNotifier(to);
            case "sms" -> new SmsNotifier(to);
            default -> throw new IllegalArgumentException("unknown channel: " + channel);
        };
    }
}

// 2. Factory Method
interface RowFormatter {
    String format(List<String> cols);
}

abstract class ReportExporter {
    // 手順は共通。何で整形するかだけをサブクラスが決める
    public final String export(List<List<String>> rows) {
        RowFormatter f = createFormatter();
        StringBuilder sb = new StringBuilder();
        for (List<String> r : rows) {
            sb.append(f.format(r)).append('\n');
        }
        return sb.toString();
    }

    protected abstract RowFormatter createFormatter(); // Factory Method
}

class CsvExporter extends ReportExporter {
    protected RowFormatter createFormatter() {
        return cols -> String.join(",", cols);
    }
}

class TsvExporter extends ReportExporter {
    protected RowFormatter createFormatter() {
        return cols -> String.join("\t", cols);
    }
}

// 3. Abstract Factory
interface Button { String render(); }
interface Dialog { String render(); }

interface UiFactory {
    Button createButton();
    Dialog createDialog();
}

class LightUiFactory implements UiFactory {
    public Button createButton() { return () -> "[light button]"; }
    public Dialog createDialog() { return () -> "[light dialog]"; }
}

class DarkUiFactory implements UiFactory {
    public Button createButton() { return () -> "[dark button]"; }
    public Dialog createDialog() { return () -> "[dark dialog]"; }
}

public class Main {
    static void drawScreen(UiFactory ui) {
        System.out.println(ui.createButton().render() + " " + ui.createDialog().render());
    }

    public static void main(String[] args) {
        NotifierFactory.create("email", "[email protected]").send("ビルド成功");
        NotifierFactory.create("sms", "090-0000-0000").send("ビルド失敗");

        List<List<String>> rows = List.of(List.of("id", "name"), List.of("1", "factory"));
        System.out.print(new CsvExporter().export(rows));
        System.out.print(new TsvExporter().export(rows));

        drawScreen(new DarkUiFactory());
        try {
            NotifierFactory.create("fax", "x");
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

実行結果は次のとおりです(TSVの区切りはタブ)。この例は設計パターンの説明用で、値に含まれるカンマ・引用符・改行・タブのエスケープは省略しています。実用のCSV出力には、それらを扱えるライブラリを使ってください。

mail to [email protected]: ビルド成功
sms to 090-0000-0000: ビルド失敗
id,name
1,factory
id	name
1	factory
[dark button] [dark dialog]
unknown channel: fax

Simple Factoryの default で例外を投げているのは、未知の値で null を返すと、呼び出し側から遠く離れた場所で NullPointerException が出て原因をたどりにくくなるためです。もう1つ、Notifier をsealedにしておくと、実装を足したときに permits を書き換える必要があり、許可する実装の一覧を確認できます。ただし、この例のswitchは文字列を判定しているため、生成分岐の追加漏れをコンパイラは検出しません。

Pythonでのファクトリパターン実装例

Pythonではクラスも関数のように呼び出してインスタンスを生成できるオブジェクトなので、ファクトリクラスを作らず、生成関数を辞書に登録する形が素直です。代替コンストラクタは @classmethod で書きます。Python 3.14.6と3.9.6で実行を確認しています。

from dataclasses import dataclass
from typing import Callable, Protocol


class Notifier(Protocol):
    def send(self, message: str) -> None: ...


@dataclass
class EmailNotifier:
    address: str

    def send(self, message: str) -> None:
        print(f"mail to {self.address}: {message}")


@dataclass
class SlackNotifier:
    webhook: str

    def send(self, message: str) -> None:
        print(f"slack {self.webhook}: {message}")

    @classmethod
    def from_env(cls, env: dict[str, str]) -> "SlackNotifier":
        # 代替コンストラクタ(クラスメソッドのファクトリ)
        return cls(env["SLACK_WEBHOOK"])


# 生成関数を辞書に登録する形のファクトリ
_REGISTRY: dict[str, Callable[[str], Notifier]] = {
    "email": EmailNotifier,
    "slack": SlackNotifier,
}


def create_notifier(channel: str, to: str) -> Notifier:
    factory = _REGISTRY.get(channel)
    if factory is None:
        raise ValueError(f"unknown channel: {channel}")
    return factory(to)


create_notifier("email", "[email protected]").send("deploy done")
SlackNotifier.from_env({"SLACK_WEBHOOK": "hooks/xyz"}).send("deploy done")
try:
    create_notifier("fax", "x")
except ValueError as e:
    print(e)
mail to [email protected]: deploy done
slack hooks/xyz: deploy done
unknown channel: fax

辞書登録にすると、新しい通知先は _REGISTRY に1行足すだけで増やせ、if/elif の列が伸び続けることもありません。Pythonで抽象基底クラスを使ったFactory Methodの書き方や、ファクトリ関数・classmethodの使い分けはPythonのFactory Method(ファクトリメソッド)実装で詳しく扱っています。

標準ライブラリとフレームワークに組み込まれたファクトリ

ファクトリパターンは自分で書く前に、すでに毎日使っています。どこにあるかを知っておくと、設計の判断材料になります。

JDKの静的ファクトリメソッド

List.of()(Java 9以降)は変更不可のリストを返しますが、戻り値の具体クラスは公開されていません。JDK 25(Temurin 25.0.4.1)で戻り値の getClass() を出力すると、要素が1〜2個なら ImmutableCollections$List12、0個や3個以上なら ImmutableCollections$ListN が返り、空リストのインスタンスを再利用します。ただし、これらは実装上の詳細であり、List.of()のAPI仕様は具体クラスやインスタンスの同一性を保証しません。要素数で実装を切り替えたりインスタンスを使い回したりできるのは、コンストラクタではなくファクトリメソッドだからです。Calendar.getInstance() は既定のタイムゾーンとロケールに応じたCalendarを返し、DocumentBuilderFactory.newInstance() はXMLパーサの実装を実行時に選びます。いずれも呼び出し側は具体クラス名を書きません。

SpringのBeanFactoryとFactoryBean

Spring Frameworkでは、DIコンテナそのものが BeanFactory という巨大なファクトリです。Spring Bootでコンテナ管理のBeanを生成する場合、生成の手順が込み入ったBeanは @Bean メソッドか、FactoryBean インタフェースを実装したクラスで作らせるのが定石です。@Bean を付けたメソッドは、それ自体がコンテナに呼ばれるファクトリメソッドです。

Pythonの代替コンストラクタと.NETのHTTPクライアント生成

Pythonの dict.fromkeys() や datetime.fromtimestamp() はクラスメソッドによる代替コンストラクタで、静的ファクトリメソッドと同じ発想です。.NETでは IHttpClientFactory が、名前付きの設定を持つ HttpClient の生成を担います。Microsoftのドキュメントによると、ファクトリは内部のハンドラ(接続プールを持つ)をキャッシュして寿命を管理し、既定の寿命は2分です。ハンドラを必要以上に作るとソケット枯渇や接続の遅延が起き、逆に使い続けるとDNSの変更に追従できません。こうした管理をファクトリに集約できます。ただし、DNS変更に追従するには、生成されたHttpClientを長期間保持せず、適切なタイミングで新しく取得する必要があります。

ファクトリパターンの使いどころと使わない方がいい場面

ファクトリの導入条件:型選択・生成手順・拡張・テスト

次のどれかに当てはまるなら、ファクトリを挟む価値があります。1つ目は、作る型が設定値や入力で実行時に変わるとき。2つ目は、生成に設定の読み込みや依存オブジェクトの組み立てが要り、呼び出し側に書くと長くなるとき。3つ目は、対応する型が今後増える見込みがあるとき。4つ目は、テストで本物の代わりに偽物(モック)を差し込みたいときです。本記事の例でいえば、EmailNotifier と SmsNotifier を設定で切り替える処理が1つ目、SlackNotifier.from_env() のように環境変数を読んで組み立てる処理が2つ目に当たります。2つ目と4つ目は、型が1種類でも成り立ちます。

ファクトリを挟まない方がいい場面

作る型が1つで、増える予定もなく、生成も new 1行で済むなら、ファクトリは不要です。UserFactory.create() が中で new User() を返すだけなら、読む人がファクトリを開いて確かめる手間が増えるだけです。「将来増えるかもしれない」だけを理由に先回りで作るのはやめ、2つ目の型が現れた時点で導入すれば間に合います。

もう1つの失敗パターンは、Simple Factoryの switch が数十分岐に育つことです。分岐数にかかわらず、型の追加で既存の分岐を修正するファクトリ自体は、その変更に対して閉じていません。数十分岐になると、さらに変更やレビューの負担が増えます。この段階に来たら、Pythonの例のような登録方式(マップへの登録)に切り替えるか、DIコンテナに生成を任せる設計へ移るのが妥当です。

ファクトリパターンとStrategy・Builder・DIの違い

Strategyパターンとの違い

ファクトリは「何を作るか」を切り替える生成のパターン、Strategyは「どう処理するか」を切り替える振る舞いのパターンです。両者は対立せず、設定値から適切なStrategyの実装をファクトリで作って渡す、という併用がよくあります。Strategy側の実装例はStrategyパターンの実装例と使いどころを参照してください。

Builderパターンとの違い

ファクトリは「どのクラスを作るか」を隠し、多くは1回の呼び出しで完成品を返します。引数の多いオブジェクト向けのBuilderでは、setName() のように1項目ずつ組み立てる手順を整理します。生成する実装や製品群の選択にはファクトリ、複数段階の構築手順と完成品の表現の分離にはBuilderが向きます。GoFのBuilderでは、同じ構築手順から異なる表現を作ることもできます。詳しくはBuilderパターンの基本的な概要と特徴で解説しています。

依存性の注入(DI)との関係

ファクトリを使っても、呼び出し側が NotifierFactory.create() を自分で呼ぶ限り、ファクトリという具体物への依存は残ります。DIはさらに一歩進め、必要なオブジェクトを外から渡してもらうことで、生成を呼ぶ行為そのものを呼び出し側から消します。DIコンテナは内部でファクトリを使っており、両者は代替ではなく層の違いです。考え方の違いは依存性の注入(DI)と依存性逆転の原則(DIP)の違いで整理しています。

ファクトリパターンに関するよくある質問

ファクトリパターンとは何ですか?読み方は?

読みは「ファクトリーパターン」または「ファクトリパターン」で、英語では Factory pattern です。オブジェクトの生成処理を専用のクラスやメソッドに集め、呼び出し側が具体クラスを知らずに済むようにする設計です。GoFの正式名はFactory MethodとAbstract Factoryで、「ファクトリパターン」はそれらとSimple Factoryを含む総称として使われます。

ファクトリパターンの使いどころは?

作る型が実行時に変わる、生成手順が長い、型が今後増える、テストでモックに差し替えたい、のいずれかに当てはまるときです。型が1つで生成も new 1行なら、挟まない方が読みやすくなります。

Factory MethodとAbstract Factoryの違いは?

Factory Methodは1種類の製品の生成をサブクラスに任せ、継承で作り分けます。Abstract Factoryはボタンとダイアログのような関連製品の一式を作るファクトリを丸ごと差し替え、組み合わせの整合を保ちます。単品か一式かが分かれ目です。

Javaでファクトリクラスはどう書きますか?

共通のインタフェースを定め、ファクトリのstaticメソッドが引数に応じて実装クラスを返す形が基本です。掲載例はJava 17以降のsealedインタフェースとswitch式を使います。ただし、文字列による生成分岐の追加漏れはコンパイル時には検出できないため、対応する通知先ごとのテストが必要です。本記事のJava実装例がそのまま動く最小構成です。

ファクトリとDIコンテナはどちらを使うべきですか?

SpringやASP.NET CoreのようにDIコンテナがある環境では、まずコンテナに生成を任せ、コンテナで表しにくい条件付きの生成だけをファクトリ(Springなら @Bean メソッドや FactoryBean)に書きます。コンテナの無い小さなスクリプトやライブラリでは、ファクトリ関数1つで足ります。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.09 テックブログ IDCFクラウド(IDCフロンティア)不正アクセス・ランサムウェア:影響先・復旧・データは戻るか
  2. 2026.10.09 テックブログ ユニリタの不正アクセス:SaaS文書管理から漏れた顧客の認証情報と回す手順
  3. 2026.10.08 テックブログ 大阪公立大学のランサムウェア被害と仮想化基盤の停止|全授業休講に至った経緯とバックアップを守る設定
  4. 2026.10.11 テックブログ GPT-6 Solとは?6.1 Solの変更点とLuna・Astraの使い分け【2026年10月】
  5. 2026.04.07 コラム 基礎控除と給与所得控除の違い|令和8年分は104万円と74万円で年収178万円まで所得税ゼロ

RELATED POSTS 関連記事

目次