Javaを鍛える|デザインパターン全23回 #6

Prototype:よく使うチケットテンプレートの複製

  • Java
  • デザインパターン
  • Prototype
  • Javaを鍛える

📋 この記事の目次
  1. Prototypeは何のためにあるか
  2. Before:フィールドを手作業でコピーし、1つ書き忘れた
  3. After:オブジェクト自身にcopy()を持たせる
  4. こんな場面で使う/使わない
  5. まとめ
  6. 🎓 理解度テスト

TicketCraftには、よく使う定型バグ報告を「テンプレート」として保存しておき、そこから新しいチケット下書きを作る機能があります。ところが、この複製処理は、フィールドを1つずつ手で写す形で書かれていました。

public class BugTicketTemplate {
    private String title;
    private String severity;
    private String stepsToReproduce;

    public BugTicketTemplate(String title, String severity, String stepsToReproduce) {
        this.title = title;
        this.severity = severity;
        this.stepsToReproduce = stepsToReproduce;
    }

    public String getTitle() { return title; }
    public String getSeverity() { return severity; }
    public String getStepsToReproduce() { return stepsToReproduce; }
}

Prototypeは何のためにあるか

Prototypeは、「既存のオブジェクトを、そのクラスの詳細を知らなくても複製できるようにする」 パターンです。オブジェクト自身に「自分をコピーする方法」を持たせておくことで、複製のたびに呼び出し側がフィールドを1つずつ列挙する必要がなくなります。

Before:フィールドを手作業でコピーし、1つ書き忘れた

public class Main {
    public static void main(String[] args) {
        BugTicketTemplate template = new BugTicketTemplate(
            "定型: ログインエラー", "medium", "1. ログイン画面を開く 2. 誤ったパスワードを入力する");

        // テンプレートから新しいチケット下書きを作るとき、フィールドを1つずつ手で複製している
        BugTicketTemplate newTicket = new BugTicketTemplate(
            template.getTitle(),
            template.getSeverity(),
            "" // stepsToReproduceのコピーを書き忘れた
        );

        System.out.println("複製元: " + template.getStepsToReproduce());
        System.out.println("複製先: " + newTicket.getStepsToReproduce());
    }
}

実行結果:

複製元: 1. ログイン画面を開く 2. 誤ったパスワードを入力する
複製先: 

stepsToReproduce(再現手順)のコピーを1行書き忘れただけで、複製先のチケットは再現手順が空のまま作られてしまいました。フィールドが増えるたびに、こうした「コピー漏れ」のリスクも増えていきます。

After:オブジェクト自身にcopy()を持たせる

public class BugTicketTemplate {
    private String title;
    private String severity;
    private String stepsToReproduce;

    public BugTicketTemplate(String title, String severity, String stepsToReproduce) {
        this.title = title;
        this.severity = severity;
        this.stepsToReproduce = stepsToReproduce;
    }

    public BugTicketTemplate copy() {
        return new BugTicketTemplate(this.title, this.severity, this.stepsToReproduce);
    }

    public String getTitle() { return title; }
    public String getSeverity() { return severity; }
    public String getStepsToReproduce() { return stepsToReproduce; }
}
public class Main {
    public static void main(String[] args) {
        BugTicketTemplate template = new BugTicketTemplate(
            "定型: ログインエラー", "medium", "1. ログイン画面を開く 2. 誤ったパスワードを入力する");

        BugTicketTemplate newTicket = template.copy();

        System.out.println("複製元: " + template.getStepsToReproduce());
        System.out.println("複製先: " + newTicket.getStepsToReproduce());
    }
}

実行結果:

複製元: 1. ログイン画面を開く 2. 誤ったパスワードを入力する
複製先: 1. ログイン画面を開く 2. 誤ったパスワードを入力する

呼び出し側はtemplate.copy()と呼ぶだけになり、「どのフィールドをコピーすべきか」という知識はBugTicketTemplate自身の中に閉じ込められました。将来フィールドが増えても、copy()の中を1箇所直すだけで、呼び出し側はすべて正しく複製され続けます。

🧓 ベテランの現場コラム Prototypeは地味なパターンに見えますが、「コピーのたびにフィールドを列挙する」という書き方は、フィールドが増減するたびに事故が起きやすい典型パターンです。複製処理を書く場面に出くわしたら、まず「複製する責任は、複製される側のオブジェクト自身に持たせられないか」を考えてみましょう。

こんな場面で使う/使わない

  • 使う: 既存のオブジェクトをベースに、少し手を加えた新しいオブジェクトを作る場面が繰り返し出てくる場合
  • 使わない: そもそも複製する場面がほとんどなく、都度きちんと1から組み立てた方が分かりやすい場合

まとめ

  • Prototypeは「複製する責任をオブジェクト自身に持たせる」パターン
  • 呼び出し側がフィールドを手作業で列挙してコピーする書き方は、コピー漏れの温床になる
  • フィールドが増えても、copy()の中を直すだけで複製処理全体が追従する

🎓 理解度テスト

学んだ内容を確認しましょう。全3問。