Help us understand the problem. What is going on with this article?

JavaでProxyパターン

はじめに

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パターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

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

参考文献

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした