今回からは、Chain of Responsibilityを取り上げる。

本連載では、第49回からデザインパターンとObjective-C + Cocoaに関する話題を取り上げているが、そもそもデザインパターンを取り扱おうと思ったきっかけは、Chain of Responsibilityである。Cocoaのフレームワークの中では、Chain of Responsibilityがとても大きな役割を占めている。そして、それにはObjective-Cの動的な特性を巧妙に利用している。しかも、単にプログラミングテクニックとして面白いだけではなく、実際のデスクトップアプリケーションを作成する上で、この上なく有用なのだ。

このChain of Responsibilityをどうしても紹介したかったのだが、それには前提となる知識がいくつか必要だ。Objective-Cでのメソッドの動的な調査であったり、セレクタであったり、ターゲット・アクションの考え方であったり、Interface Builderによるアクションの接続であったり、だ。

ようやく、これらの準備が整った。いよいよ、巧妙であり精緻であり、かつ動的なプログラミング言語が実際のアプリケーションに対してどれだけ有用であるかを示す希有な例である、Chain of Responsibilityの話を開始しよう。

Chain of Responsibilityパターンとは

例によって、まずはGoF的な定義の紹介から始めよう。

GoF本によれば、Chain of Responsibilityとは、複数のオブジェクトに要求を処理する機会を与えるパターン、ということになる。まず、ある要求があり、それを処理するオブジェクトを定義する。さらに、そのオブジェクトが複数あるとする。このような状態のとき、要求が発生したときに、それをどのオブジェクトに処理させるかを決定する必要がある。

このとき、誰が処理するかを、そのオブジェクト自身に決めさせよう、というのがChain of Responsibilityの発想だ。そのために、処理を行う複数のオブジェクトを一列、つまりチェーン状、に並べる。そして、まず先頭のオブジェクトに要求を処理をさせてみる。そのオブジェクトが要求を処理できるならば、そこで終わる。できないならば、チェーンでつながっている、次のオブジェクトにその処理を回すのだ。次のオブジェクトも処理を試み、処理できるならばそれで良し。できないならば、またその次に回す。。。ということを、続けていく。

いわば、処理を行う責任をたらい回しにしていく、「責任の連鎖」だ。これが、Chain of Responsibilityパターンである。

Chain of Responsibilityパターンの登場人物

Chain of Responsibilityの登場人物は、実質1つだ。要求を処理するオブジェクトである。このオブジェクトには、2つのことが求められる。1つは、もちろん、要求を処理すること。もう1つは、自分で要求を処理できないときに、それを次のオブジェクトに送る事だ。ここでは、要求を処理するオブジェクトをHandlerというクラスとしよう。

要求を処理する方法には、いくつかの手段が考えられるが、一番簡単なのはそれ専用のメソッドを作ってしまう事だ。サブクラスではこのメソッドを上書きする事により、必要な処理を実装するのだ。

さらに、要求をたらい回しにする手段も必要になる。このために、インスタンス変数として、successorを定義しよう。これには、チェーン状に連なった、「次の」オブジェクトが入る。自分で要求を処理できないときには、この次のオブジェクトに回すのだ。

これらを使うと、Chain of Responsibilityパターンは、次のように図示できるだろう。

Chain of Responsibilityパターンの実装

では、上の図で表した場合のChain of Responsibilityを、Objective-Cで実装してみよう。

まず、Handlerクラスだ。インスタンス変数として、_successorを定義する。これは、Handler型になる。

メソッドは、2つ定義しよう。1つは初期化メソッドで、successorを指定する。もう1つは、要求を処理するためのメソッドだ。handleRequestという名前にする。

List 1.

@interface Handler : NSObject
{
    Handler*    _successor;
}
- (id)initWithSuccessor:(Handler*)successor;
- (void)handleRequest;
@end

この2つのメソッドを実装しよう。初期化メソッドであるinitWithSuccessor:では、オブジェクトの初期化を行った後、インスタンス変数に引数を設定する。

List 2.

- (id)initWithSuccessor:(Handler*)successor
{
    self = [super init];
    if (!self) {
        return self;
    }

    _successor = successor;

    return self;
}

そしてhandleRequestメソッドであるが、このメソッドの実装がChain of Responsibilityの肝になる。このメソッドでは、自身で要求を処理できない場合、次のメソッドに要求を投げるのである。そこで、ベースクラスであるHandlerクラスでは、_successorのhandleRequestメソッドを呼び出すのだ。これにより、責任のたらい回しを行うことができる。

List 3.

- (void)handleRequest
{
    [_successor handleRequest];
}

後は、Handlerクラスのサブクラスを作り、必要に応じてhandleRequestを上書きすればよい。ここでは、Button、Dialog、Applicationという名前のサブクラスを作ってみよう。

List 4.

@interface Button : Handler
@end

@implementation Button

- (void)handleRequest
{
    NSLog(@"Handled by Button");
}

@end

List 5.

@interface Dialog : Handler
@end

@implementation Dialog
@end

List 6.

@interface Application : Handler
@end

@implementation Application

- (void)handleRequest
{
    NSLog(@"Handled by Application");
}

@end

ButtonクラスとApplicationクラスでは、handleRequestを上書きしている。Dialogクラスでは、していない。従って、Dialogに要求を投げたとしても、次のオブジェクトにたらい回しされる訳だ。

これらのオブジェクトは、次のように使う。

List 7.

    Application*    application;
    Dialog*         dialog;
    Button*         button;
    application = [[Application alloc] init];
    dialog = [[Dialog alloc] initWithSuccessor:application];
    button = [[Button alloc] initWithSuccessor:dialog];

    [button handleRequest];
    [dialog handleRequest];

それぞれのクラスのインスタンス化を行っているが、初期化時にsuccessorとなるオブジェクトを指定している。これにより、button -> dialog -> applicationというチェーンが出来上がる。

その後、handleRequestを呼ぶ事により、要求を送っている。まず、Buttonに要求を送っているが、これはButtonオブジェクトによって処理される。次にDialogに送るのだが、Dialogは要求を処理しない。そこで、「次」に位置するApplicationに要求が回され、そこで処理されるのだ。

実行結果は、次のようになる。

Result

Handled by Button
Handled by Application

これが、GoF本で紹介されているChain of Responsibilityだ。次回からは、CocoaでのChain of Responsibilityを紹介しよう。