今回からは、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を紹介しよう。