Factory Method:チケット種別ごとの生成分離
📋 この記事の目次
TicketCraftでは、Webフォームからのチケット登録画面と、バッチ取り込み処理の両方に、次のような「種類ごとの生成分岐」が個別に書かれています。
public class TicketController {
public Ticket handleNewTicketRequest(String type, String title, String detail) {
Ticket ticket;
if (type.equals("bug")) {
ticket = new BugTicket("T-" + System.nanoTime(), title, "medium");
} else if (type.equals("feature")) {
ticket = new FeatureTicket("T-" + System.nanoTime(), title, detail);
} else if (type.equals("inquiry")) {
ticket = new InquiryTicket("T-" + System.nanoTime(), title);
} else {
throw new IllegalArgumentException("未知のチケット種別: " + type);
}
return ticket;
}
}
新しいチケット種別(例えば「改善提案」)を1つ増やすたびに、この分岐をWebフォーム側・バッチ取り込み側の両方に追加しなければなりません。追加漏れが起きるのは時間の問題です。
Factory Methodは何のためにあるか
Factory Methodは、「オブジェクトの生成方法を1箇所にまとめ、呼び出し側は『何が欲しいか』だけを伝えればよい状態にする」 パターンです。社内の備品を、各部署が自分で発注するのではなく、総務部の窓口に「これが欲しい」と伝えるだけで届けてもらえるようにする、というイメージです。
Before:生成ロジックが呼び出し側に直接書かれている
public class Main {
public static void main(String[] args) {
TicketController controller = new TicketController();
Ticket t1 = controller.handleNewTicketRequest("bug", "ログイン画面が真っ白になる", "");
Ticket t2 = controller.handleNewTicketRequest("feature", "ダークモードが欲しい", "山田花子");
Ticket t3 = controller.handleNewTicketRequest("inquiry", "請求書の発行方法について", "");
System.out.println(t1.summary());
System.out.println(t2.summary());
System.out.println(t3.summary());
}
}
実行結果:
[バグ] ログイン画面が真っ白になる(重要度: medium)
[要望] ダークモードが欲しい(提案者: 山田花子)
[問い合わせ] 請求書の発行方法について
正しく動いてはいますが、TicketController自身が「どの種類のときに、どのクラスを、どう組み立てるか」まで知ってしまっています。同じ分岐が別のクラス(バッチ取り込み処理など)にもコピーされていれば、修正のたびにすべてを直す必要があります。
After:生成ロジックをFactoryクラスに集約する
public class TicketFactory {
public Ticket createTicket(String type, String title, String detail) {
switch (type) {
case "bug":
return new BugTicket("T-" + System.nanoTime(), title, "medium");
case "feature":
return new FeatureTicket("T-" + System.nanoTime(), title, detail);
case "inquiry":
return new InquiryTicket("T-" + System.nanoTime(), title);
default:
throw new IllegalArgumentException("未知のチケット種別: " + type);
}
}
}
public class TicketController {
private final TicketFactory factory = new TicketFactory();
public Ticket handleNewTicketRequest(String type, String title, String detail) {
return factory.createTicket(type, title, detail);
}
}
Mainクラス側は変更なしで、実行結果もBeforeとまったく同じです。
[バグ] ログイン画面が真っ白になる(重要度: medium)
[要望] ダークモードが欲しい(提案者: 山田花子)
[問い合わせ] 請求書の発行方法について
ポイントは、振る舞いを変えずに、生成の責任だけをTicketFactoryという1つの窓口に移したことです。バッチ取り込み処理も同じTicketFactoryを呼び出すようにすれば、新しいチケット種別を追加するときの修正箇所はTicketFactoryの1箇所だけになります。
🧓 ベテランの現場コラム Factory Methodは「単純にnewを1回別のメソッドに移しただけ」に見えるかもしれませんが、効果は絶大です。現場で「この種類を追加するのに、なぜこんなに何箇所も直す必要があるのか」と感じたら、まず疑うべきはこの生成ロジックの重複です。逆に、生成方法が本当に1箇所でしか使われていない場合にまでFactoryクラスを作ると、無駄なクラスが増えるだけになるので注意してください。
※厳密なGoFのFactory Methodは、生成用のメソッドを持つ抽象クラスを作り、サブクラスに生成を委譲する設計を指します。今回の
TicketFactoryのようにswitch文で分岐する形は、現場では「Simple Factory(単純ファクトリ)」と呼ばれることが多い派生形です。とはいえ実務で「生成ロジックを1箇所に集約する」という目的で使われるのは、圧倒的にこちらの形です。
こんな場面で使う/使わない
- 使う: 同じ種類分岐によるオブジェクト生成が、複数の呼び出し元にまたがって存在する(または存在しそうな)場合
- 使わない: オブジェクトの生成方法が単純で、呼び出し元も1箇所しかない場合。無理にFactoryクラスを作る必要はない
まとめ
- Factory Methodは「生成の責任を1つの窓口にまとめる」パターン
- 種類ごとの分岐が複数箇所に重複しているコードで特に効果を発揮する
- 呼び出し側のコード・実行結果は変えず、内部の設計だけを整理できる
🎓 理解度テスト
学んだ内容を確認しましょう。全3問。