Java
初心者
proxy
デザインパターン
GoF


はじめに

GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。


Proxyパターン


Proxyとは

日本語に訳すと「代理」を意味します。

代理のクラスが本人のクラスに成り代わってある程度の処理を行い、本当に本人が必要になった段階で本人のクラスの生成を行うパターンのことをProxyパターンと言います。

このパターンを適用することで、本人のクラスの生成を行う際に時間がかかる初期化処理に時間がかかり、ユーザーに不満を与えるという問題を解消することができます。


登場人物

Proxyパターン使用するのは以下のクラス図に登場するクラスです。

image.png


抽象クラス/インタフェース



  • Subject

    ClientクラスからみてProxyクラスとRealSubjectクラスを同様に扱う(同一視する)ための抽象クラス/インタフェースです。

    実装はサブクラスのProxyクラス、RealSubjectクラスで行います。


実装クラス


  • Proxy

    RealSubjectクラスの代理役となるクラスです。

    Clientクラスからの要求に対してできるだけの処理を行い、本当に必要になった際にRealSubjectクラスのインスタンスを生成します。

    またSubjectクラス/インタフェースの実装を行い、フィールドとして本人役のRealSubjectクラスを持ちます。


  • RealSubject

    本人役となるクラスです。

    Proxyクラスで要求を処理しきれなかった際に生成されます。また、Proxyクラスと同様にSubjectクラス/インタフェースの実装を行います。


  • Client

    Proxyクラスを経由してRealSubjectクラスを使用するクラスです。



具体例

具体例として、以下のクラスをもとに説明します。

image.png


インタフェース



  • Managementインタフェース



Management.java

public interface Management {

void setReviewProjectName(String projectName);

String getReviewProjectName();

void printReviewResult();
}


ManagementインターフェースではprojectNameの設定・取得、またレビューを行った結果を表示するメソッドを定義しています。

実際の処理については後述のManagerクラス、ManagerProxyクラスで実装します。

特段難しい点はありません。


実装クラス


  • Managerクラス


Manager.java

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クラス



ManagerProxy.java

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クラスが設定・生成されていない場合は、代理人役であるManagerProxyprojectNameに値を設定します。この時、本人役のManagerクラスは生成されておらず、代理人役であるManagerProxyが代わりに処理を行っていると言えます。

2.に関してprintReviewResultメソッドは代理人役の手には負えない処理であるため、本人役のManagerクラスが生成・設定されていない場合に、生成・設定を行っています。


実行クラス


  • Clientクラス


Client.java

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パターンに関して学びました。

以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。


参考文献