●プロキシ・パターンの応用

プロキシ・パターンを用いると、メモリの使用量を節約し、 重い処理を分散しながら、 大きなデータを構造的に扱うことができます。 資料[1] にプロキシ・パターンが説明されていますが、 更にその性質を解明してみたいと思います。

1. プロキシ

ファイルのレコードにアクセスするとき、一般に、レコード番号を渡して、 バッファにデータを格納(コピー)します。 オブジェクト指向的に見れば、それは、レコードが生成されたのでもなく 破壊されたのでもありません。 バッファ・オブジェクトの状態が変わった、もしくは、 バッファが格納しているレコード・オブジェクトが入れ替わった と考えられます。この、バッファのような オブジェクトを、プロキシと呼びます。

レコード(リアル・オブジェクト)が入れ替わったことは、 つまり、オブジェクトが異なるので、 異なるプロキシを、生成、破壊するように設計するかもしれませんが、 それは、効率の悪いプログラムになるので、 やめた方がいいでしょう。

オブジェクト全体をコピーする必要があるのは、 ファイル・アクセスのほかに、 別のプロセスや別のマシンに存在するオブジェクトを ローカルにコピーする場合があります。 いずれも、何らかの媒体を挟んだものになります。
細かい更新要求が多いとき、わざわざ媒体を挟んで更新すると 時間が掛かるので、プロキシを用います。 キャッシュと似た働きをしますが、キャッシュと異なり、 かならずローカル(メモリ)に存在していることが保証されます。

プロキシは、常に存在して多くのオブジェクトの代わりをするだけでなく、 要求があり次第、プロキシを生成するように用いることもあります。 たとえば、文章ファイルを開くとき、画像などの重たいものは、 スクロールして見えるようになってから表示させれば (画像ファイルのプロキシである、画像オブジェクトを生成すれば) よいのです。 そうすることで、最初のページを表示するまでの速度を 上げることができます。

2. 二次プロキシ・ツリー

コピー、または生成するためには、少なくともレコード番号のようなタネを ローカル・メモリ上においておく必要があります。 これを、二次プロキシと呼びます。 (これに対してオブジェクト全体のコピーを一次プロキシと呼びます。)

たとえば、100×100 個のオブジェクトがあれば、 100×100 のレコード番号配列が二次プロキシになります。 配列に限らず、次に示すようなプロキシ・ツリーを構成するなら、 それぞれのオブジェクトが二次プロキシになります。

/* 地図(親)の二次プロキシ */
struct Map {
  int    map_num;    /* 地図レコード番号 */
  Area*  area[10];
};

/* 地域(子)の二次プロキシ */
struct Area {
  int    area_num;   /* 地域レコード番号 */
  Map*   parent;
};

二次プロキシは、一次プロキシよりサイズが小さいオブジェクトなので、 すべてのリアル・オブジェクトに対するプロキシ・ツリーを作るのに、 メモリ不足になる可能性は少なくなります。
しかし、そのプロキシ・ツリーを作るために必要なデータ (レコード番号など)を、 すべて取得できない場合があります。 たとえば、目的のレコードの集合がリンク構造でまとめられていて、 それぞれのリアル・オブジェクトにアクセスしなければならない場合、 アクセス時間が問題になります。 そのような場合は、 ある範囲に限定したプロキシ・ツリーを作ることになります。 たとえば、ビューアなら、 現在ユーザに見せている部分のプロキシ・ツリーを作ります。 (その際、ビューアの構造とデータの構造(プロキシ・ツリー)は、 異なる構成、異なるツリーに別れることに注意してください。 そのあたりの、詳しいことは、Document/View について解説している資料を 参考にしてください。)

3. 実体の無いプロキシ

これまで、リアル・オブジェクトが ファイルなどに存在する場合を考えてきましたが、 リアル・オブジェクトが無い場合を考えることもできます。

たとえば、図形(多角形)を表現するオブジェクトがあるとします。 属性は、頂点の座標の配列だけです。たとえば、
(10, 10), (100, 10), (100, 100), (10, 100)
で、正方形です。
さて、この正方形をたくさん描画することになったら、どうしますか。

1つ目のやり方は、その正方形オブジェクトをたくさん用意して、 すべてのオブジェクトを描画します。
// 正方形オブジェクトを用意する
Fig  squareA = new Fig( (10, 10), (100, 10), (100, 100), (10, 100) );
Fig  squareB = new Fig( (20, 20), (110, 20), (110, 110), (20, 110) );
Fig  squareC = new Fig( (40, 40), (130, 40), (130, 130), (40, 130) );
// すべてのオブジェクトを描画する
squareA.draw();
squareB.draw();
squareC.draw();

2つ目のやり方は、それぞれの正方形オブジェクトを リアル・オブジェクトとみなして、 プロキシを作成する方法です。
// プロキシ・正方形オブジェクトを用意する
Fig  squareProxy = new Fig();
// 座標を変えながら描画する
squareProxy.exchg( 10 );
squareProxy.draw();
squareProxy.exchg( 20 );
squareProxy.draw();
squareProxy.exchg( 40 );
squareProxy.draw();

このやり方は、確かに3つの正方形が描画されますが、 1つのプロキシ・オブジェクトで済むので、メモリの節約になります。
また、リアル・オブジェクトが無いプロキシ (実体の無いプロキシ)は、パラメータを用いて 座標などの属性を修正します。 もし、リアル・オブジェクトに、 座標のほかに色や模様などの属性があるとしたら、 パラメータに関係ないそれら属性を修正する必要がないので、 処理量が減り実行速度が上がります。

まとめると、 3つの正方形がリアル・オブジェクトに相当するもの、 それらを代用しプログラム中に存在するのが 実体の無い一次プロキシ、 パラメータが実体の無い二次プロキシ(の属性)です。

参考資料


written by M.Toda from Oct.21.1997 to Oct.29.1997