Proxyパターン


 ★☆☆

Proxyパターン

要求を代理人オブジェクトが受け取って処理する

Proxyとは「代理人」という意味です。Proxyパターンは、要求を代理人オブジェクトが受け取って処理するパターンです。

現実世界で代理人というと、弁護士や税理士など本人ができない仕事をするというイメージがありますが、Proxyパターンにおける代理人オブジェクトは、本人でなくてもできるような処理を任されます。そして代理人オブジェクトでできない処理は本人オブジェクトが引き受け、処理します。この任せる行為が「委譲」です。

今日の予定を聞いたら、その予定を知っている友人が代わりに答えている感じです。でも、友人は明日のことは分からないので、それは本人が答えます。


つまり、間に挟んで、Proxyサーバ同様、対象オブジェクトの呼び出しのタイミングを変えたり、キャッシュを返したりして、対象オブジェクトの代わりの処理をしたりします。呼び出し側はProxyであることを意識しません。

このとき、呼び出しのタイミングを変えるということは次のような場合です。

生成に非常に時間が掛かる、メモリをたくさん使うなどのインスタンスや、起動時には利用するかどうか分からない機能のインスタンスをプログラムの最初に生成していては、アプリケーションの起動に時間が掛かり利用者を待たせてしまうことになります。実際に、こういう機能を利用するときになって始めて生成するというようにした方が利用者のストレスが少なくなるわけです。

また、キャッシュを返すとは次のような場合です。

一度読み込んだ情報であれば、その情報が変わらないということならばもう一度読み込む必要はないはずです。このようなとき、Proxyは読み込んだ振りをして、前に読み込んであった情報を返します。もし、まだ読み込んでいない情報ならば、本人に読み込むことを任せるわけです。


Proxyパターンの工夫は、Decoratorパターンと基本的に同じなのです(両者を比べてみてください)。では、どこが違うのか。ここが、重要なポイントです。それは、Decoratorパターンは、クラスを作る人が利用するものであり、Proxyパターンは、クラスを使う人が利用するものだということです。

同じ工夫であっても、クラスを作る人の視点から見ればDecoratorパターンであり、クラスを使う人の視点から見ればProxyパターンなのです。


例題

時間によって、適切な挨拶を返す GreetingOfEncounter というクラスがあります。これを利用して、「○○さん、」を先につけるクラスを作りなさい。


実行結果
山田さん、おはよう           
山田さん、こんにちは
山田さん、こんばんは









共通クラス

IGreeting.java
public interface IGreeting { void setClock(int hour); String getGreeting(); }
GreetingOfEncounter.java
public class GreetingOfEncounter implements IGreeting { private int state; public void setClock(int hour) { if (hour < 4 || hour >= 19) state = 3; else if (hour < 10) state = 1; else state = 2; } public String getGreeting() { if (state == 1) return "おはよう"; else if (state == 2) return "こんにちは"; else return "こんばんは"; } }

Proxyパターンを使用しない例

MyGreeting.java
public class MyGreeting extends GreetingOfEncounter { private String name; public void setName(String name) { this.name = name; } public String getGreeting() { return name + "さん、" + super.getGreeting(); } }
Main.java
public class Main { public static void main(String[] args) { MyGreeting g = new MyGreeting(); g.setName("山田"); g.setClock(7); System.out.println(g.getGreeting()); g.setClock(14); System.out.println(g.getGreeting()); g.setClock(21); System.out.println(g.getGreeting()); } }

Proxyパターンを使用した例

MyGreeting.java
public class MyGreeting { private String name; private int hour; private String className; private IGreeting real; public MyGreeting(String className) { this.className = className; } public void setName(String name) { this.name = name; } public synchronized void setClock(int hour) { if (real != null) { real.setClock(hour); } this.hour = hour; } public String getGreeting() { realize(); real.setClock(hour); return name + "さん、" + real.getGreeting(); } public synchronized void realize() { if (real == null) { try { real = (IGreeting) Class.forName(className).newInstance(); } catch (ClassNotFoundException e) { System.err.println(className + " not found"); } catch (Exception e) { e.printStackTrace(); } } } }
Main.java
public class Main { public static void main(String[] args) { MyGreeting g = new MyGreeting("GreetingOfEncounter"); g.setName("山田"); g.setClock(7); System.out.println(g.getGreeting()); g.setClock(14); System.out.println(g.getGreeting()); g.setClock(21); System.out.println(g.getGreeting()); } }

この例では、時刻によって適切な挨拶を返す GreetingOfEncounter というクラスを継承して、MyGreeting を作成しています。

しかし、これでは、MyGreeting のインスタンスを生成したときに、 GreetingOfEncounter のインスタンスも生成されてしまいます。通常は、それでもまったく問題ありませんが、setName や setClock はするけれど、meeting するかどうかわからない場合はどうでしょう。

例えば、「あっ、山田さんだ。」「今、7時だ。」「挨拶しようとしたけど行っちゃった。」せっかく生成したインスタンスが無駄になってしまいます。

もし、GreetingOfEncounter のインスタンス生成に非常に時間が掛かる、メモリをたくさん使うなどの場合に、無駄が顕著になります。

Proxyパターンでは、MyGreeting のインスタンスは生成しますが、GreetingOfEncounter のインスタンス生成は、meeting が呼ばれて(その中から realize が呼ばれて)からです。これだと、 setName や setClock はしたけれど、meeting しなかったという場合でも、無駄はありません。

また、 GreetingOfEncounter というクラスを指定しているのは、利用者のクラス(main)です。出会いの挨拶ではなく、別れの挨拶にしようと思ったとしても、利用者のクラスを書き換えるだけですみます。( GreetingOfEncounter を GreetingByParting にする)

Proxyパターンでない場合は、 MyGreeting という別のクラスを書き換えなければなりません。