Java | オブジェクト指向:ファクトリメソッドの概念

Java Java
スポンサーリンク

ファクトリメソッドとは何か

ファクトリメソッドは
new の代わりに“生成専用メソッド”を用意して、オブジェクトの作り方をそこに閉じ込める考え方」
です。

普通は new で直接コンストラクタを呼んでオブジェクトを作りますが、
その代わりに

User u = User.ofGuest();          // ← こういう“工場メソッド”を通して作る
Java

のように、「どんな状態で作るか」を表現したメソッドを用意しておき、
new を隠してしまうイメージです。

「工場(Factory)」が「作る役」を担当するので、ファクトリメソッドと呼ばれます。


なぜファクトリメソッドを使うのか(重要な目的)

1. 「どう作るか」を隠して、「何を作りたいか」を見せる

コンストラクタだけだと、呼び出し側では「引数の意味」が分かりにくくなることがあります。

User u = new User(true, false, "GUEST");
Java

これを見ても、truefalse が何を表すのか、パッと分かりません。

ファクトリメソッドを使うと、こう書けます。

User u = User.guest();
Java

「ゲストユーザを作っているんだな」と、コードを読んだ瞬間に分かります。
内部でどういうフラグや値をセットしているかは、guest() の中に隠してしまいます。

2. 作るクラスを後から差し替えやすくする

インターフェースを返すファクトリメソッドにしておくと、
「どの実装クラスを作るか」をメソッドの中で決められます。

呼び出し側はインターフェース型だけを見ておけばよく、
実装クラスを変えても呼び出し側のコードは変えずに済みます。

3. 複雑な生成ロジックを一箇所にまとめる

「引数の検証」「デフォルト値の補完」「同じものを使い回す」などのロジックを
ファクトリメソッドに集約しておくと、
呼び出し側では余計なことを考えずに「作ってもらう」だけでよくなります。


シンプルな例:状態を分かりやすくするファクトリメソッド

悪い例:コンストラクタだけで頑張る

final class User {

    private final boolean guest;
    private final String name;

    User(boolean guest, String name) {
        this.guest = guest;
        this.name = name;
    }

    boolean isGuest() { return guest; }
    String name() { return name; }
}
Java

これを使う側はこう書きます。

User u1 = new User(true, "GUEST");
User u2 = new User(false, "Taro");
Java

true / false の意味も "GUEST" の意味も、呼ばれる側の知識を知らないと分かりません。
「ここの true は“ゲスト”って意味で…」と、呼び出し側が内部仕様を知っている必要があります。

良い例:ファクトリメソッドで「意図」を表現する

final class User {

    private final boolean guest;
    private final String name;

    private User(boolean guest, String name) {   // コンストラクタは隠す
        this.guest = guest;
        this.name = name;
    }

    static User guest() {                        // ゲスト専用の工場
        return new User(true, "GUEST");
    }

    static User named(String name) {             // 名前付きユーザ専用の工場
        return new User(false, name);
    }

    boolean isGuest() { return guest; }
    String name() { return name; }
}
Java

呼び出し側はこう書けます。

User u1 = User.guest();
User u2 = User.named("Taro");
Java

guest()named(String) という名前で、「どういう状態の User を作りたいのか」がコードにハッキリ表れます。
コンストラクタは private にして、外から new させないようにしておくのがポイントです。
これで、「User の作られ方」をすべてクラスの中に閉じ込められます。


インターフェース+ファクトリメソッド(実装を隠すパターン)

実装クラスを隠したいときのファクトリメソッド

インターフェースを返すファクトリメソッドにすると、「どの実装クラスか」を隠せます。

interface Formatter {
    String format(String raw);
}
Java

このインターフェースの実装として、いくつかクラスがあるとします。

final class UpperFormatter implements Formatter {
    @Override
    public String format(String raw) {
        return raw == null ? "" : raw.toUpperCase();
    }
}

final class LowerFormatter implements Formatter {
    @Override
    public String format(String raw) {
        return raw == null ? "" : raw.toLowerCase();
    }
}
Java

普通に書くと、呼び出し側は「どのクラスを new するか」を知っている必要があります。

Formatter f = new UpperFormatter();
Java

これをファクトリメソッドで隠します。

final class Formatters {

    private Formatters() {}     // インスタンス化させない

    static Formatter upper() {
        return new UpperFormatter();
    }

    static Formatter lower() {
        return new LowerFormatter();
    }
}
Java

呼び出し側はこう書けます。

Formatter f1 = Formatters.upper();
Formatter f2 = Formatters.lower();
Java

将来、「UpperFormatter の実装を変えたい」「別のクラスに差し替えたい」となっても、
修正すべき場所は Formatters.upper() の中身だけです。

呼び出し側は「upper なフォーマッタが欲しい」としか言っていないので、
具体クラスが何であろうと関係ありません。
これが「実装を隠す」という効果です。


生成ロジックが複雑な場合のファクトリメソッド(深掘り)

検証や変換をまとめて行う

例えば「文字列から金額オブジェクトを作る」ケースを考えます。

final class Money {

    private final int amount;

    Money(int amount) {
        if (amount < 0) throw new IllegalArgumentException("minus");
        this.amount = amount;
    }

    int amount() { return amount; }
}
Java

呼び出し側が文字列から作ろうとすると、こうなります。

String raw = "1000";
int v = Integer.parseInt(raw);
Money m = new Money(v);
Java

このパターンをあちこちで繰り返すと、
変換ロジックとエラーハンドリングが散らばってしまいます。

ファクトリメソッドにまとめてしまいましょう。

final class Money {

    private final int amount;

    private Money(int amount) {
        if (amount < 0) throw new IllegalArgumentException("minus");
        this.amount = amount;
    }

    static Money of(int amount) {
        return new Money(amount);
    }

    static Money parse(String raw) {         // 文字列から作る工場
        if (raw == null || raw.isBlank()) {
            throw new IllegalArgumentException("blank");
        }
        int v = Integer.parseInt(raw.trim());
        return new Money(v);
    }

    int amount() { return amount; }
}
Java

呼び出し側はこう書くだけです。

Money m1 = Money.of(1000);
Money m2 = Money.parse("  2000 ");
Java

parse の中で

文字列の null チェック
trim
数値変換
0 未満のチェック

といった「生成に関する面倒ごと」を全部面倒見ています。
呼び出し側は「Money を作ってもらう」だけでよくなります。

生成ロジックが複雑なほど、ファクトリメソッドの価値は上がります。


ファクトリメソッドとコンストラクタの役割分担(重要な考え方)

コンストラクタは「最低限」、ファクトリメソッドで「表現力」

コンストラクタは「内部状態を成立させる最低限の処理」に絞り、
「どのパターンで作るか」「どう検証するか」「どう変換するか」は
ファクトリメソッド側に寄せる、と考えると設計が綺麗になります。

コンストラクタの役割は、「引数として渡された値をフィールドに正しくセットし、不変条件を守ること」。
ファクトリメソッドの役割は、「どの値を渡すか決めたり、元の入力から変換したりすること」。

その結果、

コンストラクタは private または package-private にする
外からは基本的にファクトリメソッド経由でしか作らせない

というスタイルが自然に出てきます。


まとめ:ファクトリメソッドを設計の武器にする

ファクトリメソッドの本質は、「作り方を隠し、意図を名前で表し、生成ロジックを一箇所に集約する」ことです。

new をどこでも気軽に呼ぶのではなく、

この状態のインスタンスを作りたい
この入力から安全に変換して作りたい
このインターフェースの実装が欲しいが、どのクラスかは隠したい

といったときに、静的メソッド(of, from, parse, guest, named など)を用意して、
「工場」としての責任をそこに持たせます。

タイトルとURLをコピーしました