Chain of Responsibilityパターンの2回目。今回からは、Cocoaにおけるこのパターンの使い方を見てみよう。

Cocoaでは、Chain of Responsibilityパターンに相当する機能は、Responder Chainと呼ばれている。名前が似ていて混乱しそうなので、気をつけてほしい。Responder Chainとした場合は、Cocoaの実装の解説になる。

チェーンを形成するもの

Chain of Responsibilityパターンは、要求を処理するオブジェクトをチェーン状に並べるものだ。であるから、まずはチェーンを形成するものを紹介しよう。

CocoaでResponder Chainを形成する際に使われるのは、NSResponderというクラスだ。通常はこのクラスのサブクラスが使われる。実際のところ、Cocoaのビューに関するすべてのクラスは、このクラスのサブクラスになる。

NSResponderでのチェーン形成は、GoF本で紹介されているものと近い。要求を処理する、「次」のオブジェクトを登録するのだ。そのために、NSResponderには次のようなメソッドが提供されている。

List 1. NSResponder.h

- (void)setNextResponder:(NSResponder*)responder;
- (NSResponder*)nextResponder;

「次」のオブジェクトを取得、設定するためのメソッドだ。

ついでに、NSResponderのインスタンス変数も見ておこう。定義されているインスタンス変数は、1つだけ。「次」のオブジェクトを表すものだ。

List 2. NSResponder.h

@interface NSResponder : NSObject <NSCoding>
{
    id _nextResponder;
}

これが、Responder Chainを形成するために、NSResponderが用意しているものだ。

要求を表すもの

チェーンが形成されたら、そこに要求を渡すことになる。次は、要求をどのように表すかを紹介しよう。

前回紹介したサンプルでは、要求を表す「もの」は特になかった。handleRequestというメソッドで要求を受け渡していたが、要求を処理する場合は、直接そのメソッドで処理を行っていた。いわば、メソッドの実装そのものが要求を表していたのである。

これは、GoF本で使われていたサンプルをそのまま踏襲したものだったが、その他の方法も議論されている。たとえば、要求を定数で表すこともできる。または、要求を表すRequestクラスを作ることもできる。

ただし、どの方法であっても、要求を処理するのはhandleRequestメソッドということになる。GoF本には、Requestクラスを使った際の方法が記載されている。それを、前回のObjective-Cのコードに反映させてみよう。

List 3.

- (void)handleRequest:(Request*)request
{
    switch ([request kind]) {
    case Help: {
        [self handleHelp];
        break;
    }
    case Print: {
        [self handlePrint];
        break;
    }
    }
}

Requestクラスは、その要求の種類をkindメソッドで取得することができる。その値をもとに処理を分岐させ、handleHelpやhandlePrintといった、それぞれの処理を行うメソッドを呼び出すことになる。

この方法では、要求の種類が増えるたびに、handleRequest:の実装を行わなくてはいけない。たとえば、新しいPreviewという要求を追加したとしよう。そして、この要求を処理するPreviewDialogというクラスを作るとする。PreviewDialogでは、次のようにhandleRequest:を実装する必要がある。

List 4.

- (void)handleRequest:(Request*)request
{
    switch ([request kind]) {
    case Preview: {
        [self handlePreview];
        break;
    }
    default: {
        [super handleReqeust:request];
    }
    }
}

このように、新規の要求を増やすたびに、その分岐を行うコードを追加していかなければいけない。

ターゲット・アクションで要求を表す

では、CocoaのResponder Chainで要求を表すものを紹介しよう。それは、ターゲット・アクションだ。Commandパターンの紹介のときに執拗に説明した、ターゲット・アクションがここで使われるのだ。NSResponderがターゲットとなり、チェーンを次々とたどっていくのだ。正確に言えば、ターゲットは変化するので、アクションだけが使われるとも言える。

Responder Chainでどのような処理が行われているか理解する助けとして、先ほどのhandleRequest:をターゲット・アクションを使って書き換えてみよう。この場合、要求を表すものはセレクタとなる。ベースクラスとなるHandleクラスで、次のように実装してやればいい。

List 5.

- (void)handleRequest:(SEL)selector
{
    if ([self respondsToSelector:selector]) {
        [self performSelector:selector];
    }
    else {
        [_successor handleRequest:selector];
    }
}

要求をセレクタで表しているので、あるクラスがその要求を処理できるかどうかは、そのセレクタに対するメソッドを実装しているかどうか、で判断することができる。実装されているならば、それを呼び出して処理は終わる。実装されていないならば、チェーンの次のオブジェクトを呼び出して、要求を回していくのだ。

これが、CocoaのResponder Chainの考え方の基礎となる。メソッドの実装を調べるrespondsToSelector:と、セレクタを指定してメソッドを呼び出すperformSelector:が使われている。つまり、動的な言語の特質を、最も根幹的なところで使っているのが分かるだろう。Cocoaアプリケーション中のほぼすべてのイベントやメッセージの伝播が、この仕組みによっているのだ。

デザインパターンの用語を使えば、Commandパターンの要求を、Chain of Responsibilityパターンで受け渡している、と言えるかもしれない。だが、GoF本によるCommandパターンはコマンドを処理する主体としての側面が強いので、あまりフィットしないだろう。ターゲット・アクションのような、コマンドの種類だけを表す非インテリジェントなコマンドならば、この解釈もできる。

次回は、Responder Chainの実際の動作について、さらに詳しく踏み込んでいこう。