本記事ではGoFのデザインパターンのプログラムの構造に関するパターンの一つである「Proxy」パターンを解説します。このパターンを一言で説明するならば、「代理人オブジェクトを立てて、本人でもなくてもできる処理を代理人オブジェクトが引き受け、本人にしかできない処理を本人のオブジェクトが実行する方法」と言えるでしょう。「Proxy」パターンをサンプルを踏まえて解説します。
「Proxy」パターンとは
先述した通りproxyパターンとは実際のオブジェクトにアクセスするのではなく、代理人オブジェクトを立てて、そのオブジェクトを介してアクセスします。このproxyパターンは下記のような時に効力を発揮します。
仮想プロキシ | リソースが重いオブジェクト(生成に時間のかかるオブジェクト)の作成を遅延したい時。 オブジェクトの振る舞いをproxyが担うことで、その間にオブジェクトを生成することができる |
保護プロキシ | オブジェクトのアクセスを制限するために利用 proxyを介さない場合、本物オブジェクトに直接アクセスするようになるため、利用を想定していない機能にまでアクセスできてしまう。これを防ぐためにproxyを介してアクセスし、アクセス権限を限定的にする |
リモートプロキシ | 別のアドレス空間にあるオブジェクトへのアクセスを制御したい時 リモートメソッド呼び出し (RMI) の実装でよく使われる |
スマートプロキシ | 本物のオブジェクトに追加の処理を提供するために利用 オブジェクトのアクセスを制御しつつロギング等の追加の処理をプロキシに追加することができる |
「Proxy」パターンの書き方
proxyパターンを実装していく上で下記の3つの基本的な構造があります。
Subject インターフェース | RealSubjectとProxyが実装する共通のインターフェース。 |
RealSubject クラス | 実際の処理を行う本物のオブジェクトのクラス。 Subjectを実装する |
Proxy クラス | RealSubject へのアクセスを制御するProxyオブジェクト。Subjectインターフェースを実装し、RealSubject への参照を保持する。 |
上記の表を元に実装していきます。まずはSubjectインターフェースを作ります。
/* RealSubjectとProxyが実装する共通のインターフェース */
public interface Subject {
void request();
}
次にSubjectを実装してRealSubjectクラスを作ります。今回の例ではRealSubjectのインスタンス化に時間のかかるクラスです。これを表現するためにコンストラクタ内でsleepを入れます。実際にはデータ量が膨大であったり、複雑な処理があったりで遅延することがあるクラスになります。
public class RealSubject implements Subject {
public RealSubject() {
// インスタンス化に時間がかかることをシミュレート
System.out.println("RealSubjectのインスタンス化中...");
try {
Thread.sleep(2000); // 2秒間スリープ
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("RealSubjectのインスタンス化完了");
}
@Override
public void request() {
System.out.println("RealSubject: リクエストを処理します");
}
}
request()メソッドは今回は標準出力で文字列を出力するだけですが、本来は別の処理がある機能を提供してくれるメソッドです。
次にProxyクラスをSubjectを実装して定義します。このproxyクラスにはスマートプロキシも表現したいため、Proxyクラスにロギング機能を追加します。
public class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void request() {
if (realSubject == null) {
System.out.println("Proxy: RealSubjectを作成しています...");
realSubject = new RealSubject();
}
logRequest();
realSubject.request();
}
// ロギング機能
private void logRequest() {
System.out.println("Proxy: リクエストをログに記録します...");
}
}
このProxyはRealSubjectのインスタンスを持っています。proxyからrequest()を呼んだ時、realSubjectの生成ができていなければ、request()処理を遅延してrealSubjectが生成されたのちにリクエスト処理を実施します。
さらにRealSubjectには本来、request()機能しか持っていませんが、proxyにはlogRequest()というロギング機能が備わっています。RealSubjectを直接呼び出してもロギングはできませんが、proxyを挟むことでロギング機能を利用できるようになります。
このproxyをクライアント側で利用して実行します。
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy();
// 初回リクエストではRealSubjectがインスタンス化される
proxy.request();
// 2回目以降のリクエストでは既存のRealSubjectが使用される
proxy.request();
}
}
実行結果>>
Proxy: RealSubjectを作成しています...
RealSubjectのインスタンス化中...
RealSubjectのインスタンス化完了
Proxy: リクエストをログに記録します...
RealSubject: リクエストを処理します
Proxy: リクエストをログに記録します...
RealSubject: リクエストを処理します
これで元のオブジェクトに干渉することなく、Proxy経由でRealSubjectの機能を利用することができました。Proxyには他にも負荷分散や認証認可等の機能を付加することがあります。適切に利用していきましょう。
まとめ
本記事で利用したサンプルケースのクラス図は下記になります。
最後に、「Proxy」パターンとは代理人オブジェクトを立てて、本人でもなくてもできる処理を代理人オブジェクトが引き受け、本人にしかできない処理を本人のオブジェクトが実行する方法です。Proxyには「仮想」「保護」「リモート」「スマート」のProxyがあります。うまく使っていきましょう。
コメント