今回からは、Proxyパターンを取り上げよう。これが、構造に関するパターンの最後となる。

Proxyパターンとは

Proxyとは「代理人」といった意味だ。この単語自体は、ネットワークの設定で使われる「プロキシサーバ」などで、すでにおなじみだと思う。

Proxyパターンの考え方は、ネットワークのプロキシサーバのものとほぼ同じと考えて差し支えない。ターゲットに直接アクセスするのではなく、間に代理人を置いて、それを経由してアクセスするのだ。目的は、コストのかかる処理を出来る限り延期する事にある。

例として、画像ファイルを処理する場合を考えてみよう。画像ファイルを処理するために、Imageというクラスを作るとする。Imageでは、初期化時に処理する画像ファイルのパスを与えるとする。だが、この時点で勢い込んで画像ファイルの読み込みをするのはあまりよくない。なぜなら、画像のような大きなファイル(ものによっては数百MB)を読み込むのは時間がかるからだ。

もちろん、画像を処理する以上は、いつかはファイルを読み込まなくてはいけない。だが時間のかかる処理なので、出来るだけ後に後にと引き延ばしたいのだ。これにより、アプリケーションの操作感が良くなる。

そこで、直接Imageクラスを作成するのではなく、代理人となるImageProxyクラスを作成することを考えよう。ImageProxyクラスは、インスタンス変数としてImageクラスを保持して、そこへのアクセスを中継する。ImageProxyは、Imageと同じAPIを持つので、アプリケーション側からはImageクラスと同等に扱うことができる。

ここでポイントとなるのは、ImageProxyは、いつImageクラスを作るのか?ということだ。初期化時にはまだ必要ない。いろいろな設定を行うときにも必要ない。必要になるのは、実際にアプリケーションが画像を表示するときだ。そのときに初めてImageクラスを作成すればよい。

これが、Proxyパターンの考え方だ。

4種類のProxy

GoF本では、実用でのProxyパターンの使われ方として、4種類のパターンを紹介している。それらを説明しよう。

まず、remote proxyと呼ばれるものだ。これは、別アドレスにあるオブジェクトにアクセスするためのものだ。つまり、プロセス間通信を実現する。実例として挙げられているのは、CocoaのNSProxyだ。これについては、次回じっくり説明したい。

次に紹介されているのは、virtual proxy。これは、コストのかかるオブジェクトの作成を出来る限り先送りするタイプのものだ。先ほど紹介した、ImageとImageProxyがこれにあたる。

ちなみに、Cocoaで画像処理を担当するNSImageクラスも、画像の読み込みを遅延させることができる(initByReferencingFile:メソッドを使う)。だが、プロキシとして別クラスを用意しているわけではないので、Proxyパターンには分類しづらい。

次は、protection proxyだ。これはオブジェクトへのアクセスを制御するものとなる。オブジェクトごとにアクセス権を変えたいときに有効である、と説明されている。

最後は、smart referenceだ。これは、C++などでスマートポインタを呼ばれているものである。メモリアドレスを表すポインタへのアクセスを監視して、参照回数を覚えておくことにより、自動的なメモリ管理を実現しようとするものだ。

Objective-Cでは、Objective-C 2.0より、ようやくガベージコレクションが導入されるので、ランタイムレベルでこの種の処理が行われることになるだろう。

Proxyパターンの登場人物

実際にProxyパターンを実装してみよう。今回取り上げるのは、virtual proxyにあたるものである。

登場するクラスは2つだ。まず、実際の処理を行うRealSubjectクラス。Clientとしては、このクラスにアクセスするのが目的となる。ただし、このクラスにはとてもコストのかかる処理がある。その処理を行うのは、出来るだけ後にしたい。

そこで、ClientとRealSubjectの間に位置するのがProxyクラスだ。Proxyクラスは、RealSubjectの作成を行い、参照を保持する。どのタイミングで作成を行うかは、Proxyクラスに委ねられる。Clientは、Proxyクラスを作成し、その参照を保持することになる。

Proxyパターン

Proxyパターンの実装

では、ソースコードだ。まず、RealSubjectクラスを、次のように定義する。

List 1.

@interface RealSubject : NSObject
{
}

- (void)setArguments:(NSArray*)arguments;
- (void)request;
@end

RealSubjectは、2つのメソッドを持っている。まず、setArguments:で設定を行い、requestで必要な処理をやらせる。そして、RealSubjectは、初期化するのに非常に時間がかかるものとする。だから、このクラスの作成は、出来るだけ後に回したい。

このクラスへのアクセスを代行するのが、Proxyクラスだ。

List 2.

@interface Proxy : NSObject
{
    RealSubject*    _realSubject;
    NSArray*        _arguments;
}

- (void)setArguments:(NSArray*)arguments;
- (void)request;
@end

Proxyクラスのメソッドは、RealSubjectと同じになる。アクセスをすべて「代行」するためのものだ。

インスタンス変数を見てみよう。まず、RealSubjectへの参照がある。それに加えて、_argumentsというものがある。これは、setArguments:で渡された値をキャッシュしておくためのものだ。

続いて、Proxyの実装だ。

List 3.

- (void)setArguments:(NSArray*)arguments
{
    _arguments = arguments;
}

- (void)request
{
    if (!_realSubject) {
        _realSubject = [[RealSubject alloc] init];
    }

    [_realSubject setArguments:_arguments];
    [_realSubject request];
}

まず、setArguments:がある。ここでは、渡された引数をキャッシュしている。この段階では、RealSubjectクラスに引数は届いていない。

続いて、requestメソッド。この中で、ようやくRealSubjectのインスタンスが作成されている。ここでコストのかかる処理が行われているわけだ。インスタンスが作成されたら、キャッシュしておいた_argumentsを渡し、requestメソッドを呼び出している。

アプリケーションからの使い勝手は、次のようになる。

List 4.

Proxy*  proxy;
proxy = [[Proxy alloc] init];
[proxy setArguments:arguments];
[proxy request];

つまり、RealSubjectとまったく同じように使えるわけだ。アプリケーションは、そのクラスがプロキシなのか実体なのか、特に意識することなく使うことができる。

次回は、CocoaでのProxyパターンの代表格、NSProxyを紹介しよう。remote proxyを使って、プロセス間通信を実現するクラスだ。

提供:毎日キャリアバン ク

毎日キャリアバンクではITエンジニア出身のキャリアコンサルタントで形成する IT専門のチームを編成し、キャリアに応じた専任コンサルタントがご相談を承り ます。キャリアチェンジから市場価値の可能性、ご収入などの相談から面接のア ドバイスまでお気軽にご相談ください。求人情報誌や転職情報サイトなどで一般 に公開されていないような「急募求人案件」も随時ご紹介が可能です。まずはご 登録ください!