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

Proxy:添付ファイルの遅延読み込み

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

📋 この記事の目次
  1. Proxyは何のためにあるか
  2. Before:作った瞬間に重い処理が走ってしまう
  3. After:Proxyで本当に必要になるまで読み込みを遅らせる
  4. こんな場面で使う/使わない
  5. まとめ
  6. 🎓 理解度テスト

TicketCraftのチケットには、エラーログなどの添付ファイルを付けられます。今のTicketAttachmentクラスは、オブジェクトを作った瞬間にファイルの中身を読み込んでしまいます。

public class TicketAttachment {
    private String fileName;
    private String content;

    public TicketAttachment(String fileName) {
        this.fileName = fileName;
        this.content = loadFromDisk(fileName); // 生成した瞬間に重い読み込みが走ってしまう
    }

    private String loadFromDisk(String fileName) {
        System.out.println("[TicketAttachment] " + fileName + " を読み込み中...(重い処理)");
        return fileName + "の中身データ";
    }

    public String getContent() {
        return content;
    }
}

チケット一覧画面では、添付ファイルの「ファイル名」だけを表示できればよく、中身までは必要ありません。ところが今の作りだと、一覧に表示するチケットの数だけ、この重い読み込み処理が毎回実行されてしまいます。

Proxyは何のためにあるか

Proxyは、「本物の代わりに窓口となるオブジェクトを立て、本当に必要になるまで重い処理を遅らせる」 パターンです。分厚い専門書を、必要になるまで書店に取り寄せを頼まず、目次だけ確認しておいて本当に読みたくなったときに初めて取り寄せるようなものです。

Before:作った瞬間に重い処理が走ってしまう

public class Main {
    public static void main(String[] args) {
        TicketAttachment attachment = new TicketAttachment("エラーログ.txt");
        System.out.println("一覧に表示するだけなら、まだ中身は要らないのに読み込まれてしまった");
    }
}

実行結果:

[TicketAttachment] エラーログ.txt を読み込み中...(重い処理)
一覧に表示するだけなら、まだ中身は要らないのに読み込まれてしまった

ファイル名を表示するだけのつもりが、オブジェクトを作った時点で中身の読み込みが実行されてしまっています。

After:Proxyで本当に必要になるまで読み込みを遅らせる

まず、共通のインターフェースを定義します。

public interface Attachment {
    String getContent();
}

実際に重い読み込みを行う本体クラスと、それを代理するProxyクラスを用意します。

public class RealTicketAttachment implements Attachment {
    private String fileName;

    public RealTicketAttachment(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public String getContent() {
        System.out.println("[RealTicketAttachment] " + fileName + " を読み込み中...(重い処理)");
        return fileName + "の中身データ";
    }
}

public class AttachmentProxy implements Attachment {
    private String fileName;
    private RealTicketAttachment real;

    public AttachmentProxy(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public String getContent() {
        if (real == null) {
            real = new RealTicketAttachment(fileName);
        }
        return real.getContent();
    }
}
public class Main {
    public static void main(String[] args) {
        Attachment attachment = new AttachmentProxy("エラーログ.txt");
        System.out.println("一覧表示: エラーログ.txt(中身はまだ読み込まれていない)");

        // 実際に中身が必要になったときだけ読み込まれる
        System.out.println(attachment.getContent());
    }
}

実行結果:

一覧表示: エラーログ.txt(中身はまだ読み込まれていない)
[RealTicketAttachment] エラーログ.txt を読み込み中...(重い処理)
エラーログ.txtの中身データ

AttachmentProxyを作った時点では、まだ重い読み込みは走っていません。一覧画面はファイル名だけを持つAttachmentProxyを扱っておき、ユーザーが実際に添付ファイルを開いたとき(getContent()が呼ばれたとき)に初めてRealTicketAttachmentが生成され、読み込みが実行されます。

🧓 ベテランの現場コラム Proxyは「重い処理を先延ばしにする(遅延読み込み)」以外にも、「アクセス権限のチェックを挟む」「リモートのオブジェクトをローカルにあるかのように見せる」など、いくつかの目的で使われる懐の広いパターンです。共通しているのは、呼び出し側からは本物とProxyの違いが見えないようにインターフェースを揃えることです。今回の例でも、Main側はAttachmentインターフェースしか知らず、相手がRealTicketAttachmentAttachmentProxyかを意識していません。

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

  • 使う: オブジェクトの生成や処理が重く、本当に必要になるまで先延ばしにしたい場合。アクセス制御を挟みたい場合にも応用できる
  • 使わない: 生成コストがそもそも軽く、遅延させる恩恵がほとんどない場合。無理にProxyを挟むと、コードが1段階複雑になるだけになる

まとめ

  • Proxyは「本物の代わりに窓口を立て、重い処理を本当に必要になるまで遅らせる」パターン
  • 呼び出し側は共通インターフェースだけを見ればよく、本物とProxyの違いを意識しない
  • 遅延読み込み以外にも、アクセス制御など様々な目的に応用できる

🎓 理解度テスト

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