【Java】Proxy パターン【デザインパターン】
目次
Proxy パターンについて
Proxy パターンとは
- Proxyパターンは、忙しくて仕事ができない"本人"オブジェクトの代わりに、"代理人"オブジェクトが一部の仕事をこなすパターンです
- オブジェクト指向では "本人" も "代理人"もオブジェクトとなります
- 代理人ができる仕事の範囲を超える仕事がきたら、代理人が本人に相談します
サンプルプログラム
- Proxy パターンを使って、名前付きのプリンタで、画面に文字列を表示する例を取り上げます
- 各クラスの役割は以下のようになっています
名前 | 役割 |
---|---|
Printable(Subject) | PrinterとPrinterProxy共通のインターフェース |
PrinterProxy(Proxy) | 名前つきのプリンタを表すクラス(代理人) |
Printer(RealSubject) | 名前つきのプリンタを表すクラス(本人) |
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に分けることで、プログラムの部品化が進み、個別に機能を加えることができます(分割統治)
本日もお疲れ様です😊