0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

本記事は、GoFデザインパターン解説シリーズの1つです。

今回で構造に関するパターンは最後、Proxy(プロキシ)パターンを解説します。

「プロキシサーバー」という言葉でお馴染みの、代理・仲介という考え方をオブジェクト指向設計に応用したパターンです。

解決したい課題

「あるオブジェクトへのアクセスに対して、直接アクセスさせる前に、何らかの制御(アクセス制限、遅延生成、キャッシュ、ログ記録など)を挟みたい」という場面があります。

例えば、非常に生成コストの高い「高解像度画像」オブジェクトがあるとします。

この画像を画面に一覧表示するアプリで、画面に実際に表示されるまでは画像の読み込み処理を遅らせたい(遅延初期化したい)が、利用側のコードは「画像オブジェクトがある」という前提のまま、これまで通り扱いたいとします。

かといって、画像クラス自体に「遅延読み込みのロジック」を書き込むと、本来の責務(画像を表示すること)と、生成タイミングの制御という別の関心事が混ざってしまいます。

Proxyパターンは、本来のオブジェクトと同じインターフェースを持つ「代理(Proxy)」オブジェクトを間に挟み、本来のオブジェクトへのアクセスをProxyが仲介することで、この問題を解決します。

image.png

クラス構成

  • Subject(サブジェクト): 本来のオブジェクト・Proxyの両方に共通するインターフェース
  • RealSubject(本来のオブジェクト): 実際の処理を行う、本体のクラス
  • Proxy(プロキシ): Subjectを実装しつつ、内部でRealSubjectへのアクセスを制御する代理クラス

image.png

Javaでの実装例

先ほどの、高解像度画像の遅延読み込みを例に実装してみます(この用途のProxyは特にVirtual Proxy(仮想プロキシ)と呼ばれます)。

Subject:画像共通のインターフェース

public interface Image {
    void display();
}

RealSubject:本来の(生成コストが高い)画像クラス

public class HighResolutionImage implements Image {
    private final String filename;

    public HighResolutionImage(String filename) {
        this.filename = filename;
        loadFromDisk(); // コンストラクタの時点で、重い読み込み処理が実行される
    }

    private void loadFromDisk() {
        System.out.println("[HighResolutionImage] " + filename + " をディスクから読み込み中(重い処理)...");
    }

    @Override
    public void display() {
        System.out.println("[HighResolutionImage] " + filename + " を画面に表示");
    }
}

Proxy:アクセスを制御する代理クラス

public class ImageProxy implements Image {
    private final String filename;
    private HighResolutionImage realImage; // 実際に必要になるまでnullのまま

    public ImageProxy(String filename) {
        this.filename = filename;
        // ここではまだ、重い読み込み処理を実行しない
    }

    @Override
    public void display() {
        if (realImage == null) {
            // 初めてdisplay()が呼ばれたタイミングで、初めて本来のオブジェクトを生成する
            realImage = new HighResolutionImage(filename);
        }
        realImage.display();
    }
}

利用側のコード

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Image> gallery = new ArrayList<>();
        gallery.add(new ImageProxy("photo1.jpg"));
        gallery.add(new ImageProxy("photo2.jpg"));
        gallery.add(new ImageProxy("photo3.jpg"));

        System.out.println("--- 画像一覧を生成しました(まだ読み込みは発生していない) ---");
        System.out.println();

        System.out.println("--- 1枚目だけ表示します ---");
        gallery.get(0).display();
    }
}

実行結果:

--- 画像一覧を生成しました(まだ読み込みは発生していない) ---

--- 1枚目だけ表示します ---
[HighResolutionImage] photo1.jpg をディスクから読み込み中(重い処理)...
[HighResolutionImage] photo1.jpg を画面に表示

image.png

3枚分のImageProxyを生成した時点では、重い読み込み処理は一切実行されていません。

display()が実際に呼ばれた1枚目についてのみ、その時点で初めてHighResolutionImageが生成され、読み込み処理が走っています。

利用側のコードはImageインターフェースしか意識しておらず、「今アクセスしているのが本物か代理か」を一切気にしていません。

Proxyパターンの種類

Proxyパターンには、目的に応じていくつかの種類があります。

種類 目的
Virtual Proxy(仮想プロキシ) 生成コストの高いオブジェクトの初期化を、実際に必要になるまで遅延させる(上記の例)
Protection Proxy(保護プロキシ) アクセス権限をチェックし、権限がない場合は本来のオブジェクトへのアクセスを拒否する
Remote Proxy(リモートプロキシ) ネットワーク越しの別プロセス・別サーバー上のオブジェクトを、あたかもローカルにあるかのように扱えるようにする
Logging/Caching Proxy アクセスのログ記録や、結果のキャッシュを行う

Decoratorパターンとの違い

前回解説したDecoratorパターンと、Proxyパターンは、「同じインターフェースを実装したクラスで元のオブジェクトをラップする」という構造上非常によく似ています。

しかし目的が異なります。

Decoratorは「機能を追加する」ことが目的で、複数のDecoratorを自由に重ねることを想定しているのに対し、Proxyは「本来のオブジェクトへのアクセス自体を管理・制御する」ことが目的で、多くの場合Proxyは1つだけで完結します。

使用場面

  • 生成コストの高いオブジェクトを、実際に必要になるまで遅延生成したい場合(Virtual Proxy)
  • 特定のユーザーや条件でのみ、本来のオブジェクトへのアクセスを許可したい場合(Protection Proxy)
  • リモートのオブジェクト(別サーバー上のリソースやRPC呼び出し先)を、ローカルのオブジェクトのように扱いたい場合(Remote Proxy)
  • 既存のクラスに手を入れずに、アクセスログの記録やキャッシュを追加したい場合

まとめ

項目 内容
解決する課題 本来のオブジェクトへのアクセスに対して、生成コストの制御・権限チェックなどを挟みたい
実現方法 本来のオブジェクトと同じインターフェースを持つProxyを間に挟み、アクセスを仲介する
種類 Virtual Proxy(遅延生成)・Protection Proxy(権限チェック)・Remote Proxy(リモートアクセス)など
Decoratorとの違い Decoratorは機能追加が目的、Proxyはアクセス自体の制御が目的

おすすめ書籍

GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。

Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]

※本リンクはアフィリエイトリンクを含みます。

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?