はじめに
GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。
Proxyパターン
Proxyとは
日本語に訳すと「代理」を意味します。
代理のクラスが本人のクラスに成り代わってある程度の処理を行い、本当に本人が必要になった段階で本人のクラスの生成を行うパターンのことをProxyパターンと言います。
このパターンを適用することで、本人のクラスの生成を行う際に時間がかかる初期化処理に時間がかかり、ユーザーに不満を与えるという問題を解消することができます。
登場人物
Proxyパターン使用するのは以下のクラス図に登場するクラスです。
抽象クラス/インタフェース
-
Subject
Client
クラスからみてProxy
クラスとRealSubject
クラスを同様に扱う(同一視する)ための抽象クラス/インタフェースです。
実装はサブクラスのProxy
クラス、RealSubject
クラスで行います。
実装クラス
Proxy
RealSubject
クラスの代理役となるクラスです。
Client
クラスからの要求に対してできるだけの処理を行い、本当に必要になった際にRealSubject
クラスのインスタンスを生成します。
またSubject
クラス/インタフェースの実装を行い、フィールドとして本人役のRealSubject
クラスを持ちます。RealSubject
本人役となるクラスです。
Proxy
クラスで要求を処理しきれなかった際に生成されます。また、Proxy
クラスと同様にSubject
クラス/インタフェースの実装を行います。Client
Proxy
クラスを経由してRealSubject
クラスを使用するクラスです。
具体例
インタフェース
-
Managementインタフェース
public interface Management {
void setReviewProjectName(String projectName);
String getReviewProjectName();
void printReviewResult();
}
Management
インターフェースではprojectNameの設定・取得、またレビューを行った結果を表示するメソッドを定義しています。
実際の処理については後述のManager
クラス、ManagerProxy
クラスで実装します。
特段難しい点はありません。
実装クラス
- Managerクラス
import java.util.Random;
public class Manager implements Management {
private String projectName;
public Manager() {
this.projectName = "無題の案件";
heavyJob("Managementのインスタンスを生成し、Rv中");
}
public Manager(String projectName) {
this.projectName = projectName;
heavyJob("Managementのインスタンス(" + projectName + ")を生成し、Rv中");
}
@Override
public void setReviewProjectName(String projectName) {
this.projectName = projectName;
}
@Override
public String getReviewProjectName() {
return projectName;
}
private void heavyJob(String msg) { // 1.生成時に必要(という設定)な重い処理
System.out.print(msg);
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.print("・");
}
System.out.println("Rv完了。");
}
@Override
public void printReviewResult() { // 2.乱数によってreviewの結果を振り分けて出力する処理
System.out.println("----------Rv結果----------");
Random rnd = new Random();
int ran = rnd.nextInt(10);
if (ran < 5) {
System.out.println(projectName + "は却下されました。\n");
} else if (ran > 5) {
System.out.println(projectName + "は承認されました。\n");
}
}
}
Manager
クラスは本人役のクラスです。代理役であるManegerProxy
クラスには手に負えない処理を行います。
ここではprintReviewResult
がその処理に当たります。
ポイントは以下の2点です。
1.コンストラクタ内で生成時に必要(という設定)だが、時間の掛かる重い処理であるheavyJob
メソッドを呼んでいる。
2.printReviewResult
をオーバーライドし、reviewの結果を表示している。
補足の説明を行います。
Manager
クラスの生成には初期設定としてheavyJob
メソッドを実行する必要があります。しかし、この処理は重い処理であり、時間もかかるため、代理人役であるManegerProxy
クラスでは手に負えない処理(printReviewResult
)が呼ばれるまではManager
クラスを生成するのは得策ではありません。そのため後述のManegerProxy
クラスではプロジェクト名を取得・設定するだけの処理(getReviewProjectName
,setReviewProjectName
)は代理人役の自身が行い、printReviewResult
が呼ばれた際に、本人役のManeger
クラスを生成・設定します。
-
ManagerProxyクラス
public class ManagerProxy implements Management {
private String projectName;
private Manager real;
public ManagerProxy() {
this.projectName = "無題の案件";
}
public ManagerProxy(String projectName) {
this.projectName = projectName;
}
@Override
public synchronized void setReviewProjectName(String projectName) {
// 1.本人役が生成・設定されていれば本人役にプロジェクト名を設定する。
if (real != null) {
real.setReviewProjectName(projectName);
}
// 1.本人役が生成・設定されていなければ代理人役にプロジェクト名を設定する。
this.projectName = projectName;
}
@Override
public String getReviewProjectName() {
return projectName;
}
@Override
public void printReviewResult() {
realize();
real.printReviewResult();
}
private synchronized void realize() {// 2.本人役が生成・設定されていない場合に本人役を生成・設定する
if (real == null) {
real = new Manager(projectName);
}
}
}
ManagerProxy
クラスはManager
クラスの代理人役となるクラスです。
ポイントは以下の2点です。
1.setReviewProjectName
をオーバーライドしている。
2.printReviewResult
メソッド内でrealize
メソッドを呼んだ後に、本人役のprintReviewResult
メソッドを呼んでいる。
補足で説明を行います。
1.に関してsetReviewProjectName
が呼ばれた際に、本人役のManager
クラスが設定・生成されていない場合は、代理人役であるManagerProxy
のprojectName
に値を設定します。この時、本人役のManager
クラスは生成されておらず、代理人役であるManagerProxy
が代わりに処理を行っていると言えます。
2.に関してprintReviewResult
メソッドは代理人役の手には負えない処理であるため、本人役のManager
クラスが生成・設定されていない場合に、生成・設定を行っています。
実行クラス
- Clientクラス
public class Client {
public static void main(String[] args) {
Management m1 = new ManagerProxy("A案件");
System.out.println("プロジェクトRvを開始します。");
System.out.println("Rv中のプロジェクトは" + m1.getReviewProjectName() + "です。");
m1.setReviewProjectName("B案件");
System.out.println("Rv中のプロジェクトは" + m1.getReviewProjectName() + "です。");
m1.printReviewResult();
Management m2 = new ManagerProxy();
System.out.println("プロジェクトRvを開始します。");
System.out.println("Rv中のプロジェクトは" + m2.getReviewProjectName() + "です。");
m2.setReviewProjectName("C案件");
System.out.println("Rv中のプロジェクトは" + m2.getReviewProjectName() + "です。");
m2.printReviewResult();
}
}
ManagerProxy
クラスのインスタンスを生成し名前の取得を設定を行っています。その後、printReviewResult
メソッドを呼び出すことで本人役のManager
クラスのインスタンスを生成し、処理を行っています。
実行結果
Client.java
を実行した結果は以下になります。
重い処理(heavyJob
)が実行された後に、乱数によってRv結果がランダムに出力されていることが分かります。
プロジェクトRvを開始します。
Rv中のプロジェクトはA案件です。
Rv中のプロジェクトはB案件です。
Managementのインスタンス(B案件)を生成し、Rv中・・・・・Rv完了。
----------Rv結果----------
B案件は却下されました。
プロジェクトRvを開始します。
Rv中のプロジェクトは無題の案件です。
Rv中のプロジェクトはC案件です。
Managementのインスタンス(C案件)を生成し、Rv中・・・・・Rv完了。
----------Rv結果----------
C案件は承認されました。
メリット
proxyパターンのメリットは以下になります。
1.初期化に必要な時間を短縮し、ユーザーの利便性を高めることができる。
2.代理人役と本人役に切り分けることで部品化が図れる。
まとめ
本当に必要になるまで本人ではなく代理人に処理を行わせるProxyパターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。
また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。