本文へスキップ
BecomeCoder

Java MVVMコース · 第3章 Factory ― 生成をひとまとめに · レッスン9

なぜ生成をFactoryにまとめるか

ローカル実施

導入

前章でTaskItemnew TaskItem(id, new TaskTitle(title), priority)という形で作れるようになりました。ですが、この呼び出しを「タスク追加の画面」「サンプルデータの読み込み」「テストコード」など、あちこちに書き散らかすとどうなるでしょうか。あとで「IDの発行ルールを変えたい」「優先度の既定値をMEDIUMからLOWに変えたい」となったとき、全ての呼び出し箇所を探して直すはめになります。Factoryは、この「生成のルール」を1か所に閉じ込めるパターンです。

図解

flowchart TB
    subgraph S["Factoryが無い場合"]
        A1["画面のコード<br/>new TaskItem(nextId(), new TaskTitle(t), Priority.MEDIUM)"]
        A2["インポート機能のコード<br/>new TaskItem(nextId(), new TaskTitle(t), Priority.MEDIUM)"]
        A3["テストのコード<br/>new TaskItem(nextId(), new TaskTitle(t), Priority.MEDIUM)"]
    end
    subgraph T["Factoryがある場合"]
        F["TaskFactory#create<br/>(検証・ID発行のルールを1か所に)"]
        B1["画面のコード<br/>factory.create(t)"] --> F
        B2["インポート機能のコード<br/>factory.create(t)"] --> F
        B3["テストのコード<br/>factory.create(t)"] --> F
    end

生成のパターンそのもの(Factory Method / Abstract Factory)の詳しい解説はC#デザインパターンコースのレッスン1にあります。ここでは「タスク管理アプリのどこで使うと嬉しいか」に絞って確認します。

説明

public class Person {
    private final String id;
    private final String name;
    private final int age;

    public Person(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() { return id; }
    public String getName() { return name; }
    public int getAge() { return age; }
}
public class PersonFactory {
    private static int nextId = 1;

    public static Person create(String name, int age) {
        if (age < 0) throw new IllegalArgumentException("年齢は0以上です");
        return new Person(String.valueOf(nextId++), name.trim(), age);
    }
}
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class PersonFactoryTest {
    @Test
    void zengoNoKuhaku_HaTrimSareru() {
        Person p1 = PersonFactory.create("  田中  ", 30);
        assertEquals("田中", p1.getName(), "前後の空白はトリムされる(決め事を1か所に)");
    }

    @Test
    void idNoHakkoRule_Mo1KashoNiMatomatteiru() {
        Person p1 = PersonFactory.create("田中", 30);
        Person p2 = PersonFactory.create("鈴木", 25);
        assertNotEquals(p2.getId(), p1.getId(), "IDの発行ルールも1か所にまとまっている");
    }
}
  • new Person(...)の呼び出しはPersonFactory.createの中に1か所だけ
  • 「IDをどう払い出すか」「前後の空白をトリムする」「年齢は0以上」という決め事も1か所にまとまっている
  • 呼び出し側はPersonFactory.create(name, age)と書くだけでよく、生成の詳細を知らなくてよい

まとめ

  • Factoryは「newの呼び出し」と「生成時の決め事(検証・ID発行・トリムなど)」を1か所に集約するパターン
  • 呼び出し側はコンストラクタの詳細を知らなくても、正しい状態のオブジェクトを作れる
  • 次のレッスンでは、実際にTaskFactoryをTDDで実装する

次回: 実際にTaskFactory.createをTDDで実装します。