mohuneko’s blog

かんばる駆け出しエンジニアのブログです

【Java】Proxy パターン【デザインパターン】

駆け出しエンジニアがデザインパターンをもくもく勉強します

 こんな本で勉強しています🌟

目次

Proxy パターンについて

Proxy パターンとは

  • Proxyパターンは、忙しくて仕事ができない"本人"オブジェクトの代わりに、"代理人"オブジェクトが一部の仕事をこなすパターンです
  • オブジェクト指向では "本人" も "代理人"もオブジェクトとなります
  • 代理人ができる仕事の範囲を超える仕事がきたら、代理人が本人に相談します

サンプルプログラム

  • Proxy パターンを使って、名前付きのプリンタで、画面に文字列を表示する例を取り上げます
  • 各クラスの役割は以下のようになっています
名前 役割
Printable(Subject) PrinterとPrinterProxy共通のインターフェース
PrinterProxy(Proxy) 名前つきのプリンタを表すクラス(代理人)
Printer(RealSubject) 名前つきのプリンタを表すクラス(本人)

f:id:mohuNeko:20210102164846p:plain

Printerクラス

  • コンストラクタで、ダミーの重い仕事を行なっています
public class Printer implements Printable {
    private String name;
    public Printer() {
        heavyJob("Printerのインスタンスを生成中");
    }
    public Printer(String name) {                   // コンストラクタ
        this.name = name;
        heavyJob("Printerのインスタンス(" + name + ")を生成中");
    }
    public void setPrinterName(String name) {       // 名前の設定
        this.name = name;
    }
    public String getPrinterName() {                // 名前の取得
        return name;
    }
    public void print(String string) {              // 名前付きで表示
        System.out.println("=== " + name + " ===");
        System.out.println(string);
    }
    private void heavyJob(String msg) {             // 重い作業(のつもり)
        System.out.print(msg);
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.print(".");
        }
        System.out.println("完了。");
    }
}

Printableクラス

  • PrinterとPrinterProxyを同一視するためのインターフェースです
public interface Printable {
    public abstract void setPrinterName(String name);   // 名前の設定
    public abstract String getPrinterName();            // 名前の取得
    public abstract void print(String string);          // 文字列表示(プリントアウト)
}

PrinterProxyクラス

  • 代理人の役割を果たします
  • setPrinterNameで新しく名前を設定します
  • もしrealがnullでないなら(本人がすでに作られているなら)、本人の名前もnameに設定します
  • printメソッドは、代理人の守備範囲外なので、realizeメソッドで、本人(Printerクラスのインスタンス)を生成し、real.printを呼びます
    • 代理人が本人に、処理を委譲しているということになります
    • 本人が生成されているかどうかは、PrinterProxyの利用者には全くわかりません
  • PrinterクラスはPrinterProxyの存在を知りません
    • 自分が直接呼ばれているのか、PrinterProxy経由で呼ばれているのか、知らなくていいという事です
public class PrinterProxy implements Printable {
    private String name;            // 名前
    private Printer real;           // 「本人」
    public PrinterProxy() {
    }
    public PrinterProxy(String name) {      // コンストラクタ
        this.name = name;
    }
    public synchronized void setPrinterName(String name) {  // 名前の設定
        if (real != null) {
            real.setPrinterName(name);  // 「本人」にも設定する
        }
        this.name = name;
    }
    public String getPrinterName() {    // 名前の取得
        return name;
    }
    public void print(String string) {  // 表示
        realize();
        real.print(string);
    }
    private synchronized void realize() {   // 「本人」を生成
        if (real == null) {
            real = new Printer(name);
        }
    }
}

Mainクラスで動作確認

  • PrinterProxy経由でPrinterを利用するクラスです
  • 最初にPrinterProxyを生成し、getPrinterNameで名前を表示し、setPrinterNameで名前を設定します
  • Printer(本人)のインスタンスは、printメソッドを呼び出してから、生成されています
public class Main {
    public static void main(String[] args) {
        Printable p = new PrinterProxy("Alice");
        System.out.println("名前は現在" + p.getPrinterName() + "です。");
        p.setPrinterName("Bob");
        System.out.println("名前は現在" + p.getPrinterName() + "です。");
        p.print("Hello, world.");
    }
}
  • 実行結果
名前は現在Aliceです。
名前は現在Bobです。
Printerのインスタンス(Bob)を生成中.....完了。
=== Bob ===
Hello, world.

Proxy パターンのメリット

  • Proxyパターンでは、Proxyが代理人となって、できるだけ処理を肩代わりします
    • 上記の例では、Proxy役を使うことによって、実際にprintするときまで、重い処理 (インスタンス生成) を遅らせることができました :* 初期化に時間がかかる機能が多く存在するようなシステムの場合、起動時に利用しない機能まで全て初期化してしまうと、アプリケーションの起動に時間がかかります
  • PrinterProxyクラスとPrinterクラスに分けることで、プログラムの部品化が進み、個別に機能を加えることができます(分割統治)
    • PrinterProxyクラスの実装を変えたら、何を代理人が処理し、何を本人が処理するかを変更することができます

今日のポイント

  • Proxyパターンは、忙しくて仕事ができない"本人"オブジェクトの代わりに、"代理人"オブジェクトが一部の仕事をこなすパターンです
  • Subjectのおかげで、ClientはProxyとRealSubjectの違いを意識する必要がありません(Proxyが透過的である)
  • RealSubjectはProxyの存在を知る必要がありません
  • RealSubjecと Proxyに分けることで、プログラムの部品化が進み、個別に機能を加えることができます(分割統治)

 本日もお疲れ様です😊