はじめに
本記事は、GoFデザインパターン解説シリーズの1つです。
今回で構造に関するパターンは最後、Proxy(プロキシ)パターンを解説します。
「プロキシサーバー」という言葉でお馴染みの、代理・仲介という考え方をオブジェクト指向設計に応用したパターンです。
解決したい課題
「あるオブジェクトへのアクセスに対して、直接アクセスさせる前に、何らかの制御(アクセス制限、遅延生成、キャッシュ、ログ記録など)を挟みたい」という場面があります。
例えば、非常に生成コストの高い「高解像度画像」オブジェクトがあるとします。
この画像を画面に一覧表示するアプリで、画面に実際に表示されるまでは画像の読み込み処理を遅らせたい(遅延初期化したい)が、利用側のコードは「画像オブジェクトがある」という前提のまま、これまで通り扱いたいとします。
かといって、画像クラス自体に「遅延読み込みのロジック」を書き込むと、本来の責務(画像を表示すること)と、生成タイミングの制御という別の関心事が混ざってしまいます。
Proxyパターンは、本来のオブジェクトと同じインターフェースを持つ「代理(Proxy)」オブジェクトを間に挟み、本来のオブジェクトへのアクセスをProxyが仲介することで、この問題を解決します。
クラス構成
- Subject(サブジェクト): 本来のオブジェクト・Proxyの両方に共通するインターフェース
- RealSubject(本来のオブジェクト): 実際の処理を行う、本体のクラス
- Proxy(プロキシ): Subjectを実装しつつ、内部でRealSubjectへのアクセスを制御する代理クラス
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 を画面に表示
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版 [ 結城 浩 ]
※本リンクはアフィリエイトリンクを含みます。


