Commandパターンの話を続けよう。前回はGoF本で提唱されているCommandパターンの説明をしたが、今回はCocoaで使われるコマンドの話だ。もっともCocoaのコマンドは、1つのパターンと呼ぶにはおこがましいものかもしれない。とても単純なものだ。だがそれ故に、非常に根源的で、拡張可能なものになっている。
ターゲットとアクション
CocoaはGUIを持ったアプリケーションのためのフレームワークだ。であるから、ユーザインタフェースからコマンドを発行する仕組みは、その中心的な機能として組み込まれている。ターゲットとアクションと呼ばれるものだ。
例としてボタンを考えてみよう。ボタンは、もっとも基本的なGUIの部品だ。ユーザにマウスでクリックしてもらうことにより、何らかの処理を行う。
この「何らかの処理を行う」ことを、もっと分解して考えてみよう。結局、処理を行うということは、オブジェクト指向のプログラミングの中では、「あるオブジェクト」の「あるメソッド」を呼び出す事に他ならない。ならば、そのオブジェクトとメソッドを直接ボタンに登録してしまおう、というのがCocoaの発想だ。
このとき、処理を行うオブジェクトをターゲット、呼び出すメソッドのことをアクションと呼ぶ。CocoaではボタンはNSButtonというクラスになるのだが、このクラスには、このターゲットとアクションを登録するためのメソッドがある。実際には、NSButtonの親クラスであるNSControlが提供しているのだが。
List 1. NSContorl.h
- (void)setTarget:(id)object;
- (void)setAction:(SEL)selector;
ターゲットはsetTarget:、アクションはsetAction:で設定する。ターゲットはすべてのオブジェクトを表すid型になる。アクションはセレクタとして登録する。セレクタは、プログラム中でメソッドを表すためのものだ。詳しくは、本連載の第18回『メソッドとは何か(1)』を参照してほしい。
セレクタとして登録できるメソッドには、1つ条件がある。そのメソッドは、id型の引数を1つ取る必要がある。メソッドが呼び出されるときは、その引数として呼び出し元が指定される。つまり、ボタンが発行したコマンドであれば、そのボタンが引数として渡される。これを、センダ(sender)と呼ぶ。
ここまでの関係を図で表すと、次のようになるだろう。
Cocoaのコマンドモデル |
これがCocoaのコマンドモデルになる。これを、ターゲット・アクション・パラダイムと呼ぶ。
ターゲットからアクションを呼び出す
実際にボタンが登録されたターゲットのアクションを呼び出すときは、NSObjectのメソッドが使われる。performSelector:withObject:というメソッドだ。これを使うと、セレクタを指定して、オブジェクトのメソッドを呼び出すことができる。
つまり、ターゲットとアクションが与えられたら、次のようにして呼び出すことができる。
List 2.
id target;
SEL action;
// ターゲットとアクションを指定する
...
// ターゲットのアクションを呼び出す
[target performSelector:action withObject:self];
これだけでオッケーだ。引数としてselfを渡しているのは、センダとして自分自身を指定するためだ。
セレクタを指定してメソッドを呼び出すことのできる、Objective-Cの動的な性質を最大限に利用している。
ターゲット・アクション・パラダイムで必要にして充分
つまり、CocoaでのCommandパターンは何か、と問われれば、それはターゲットとアクションのペアである、と答えることができる。特別なクラスは必要ない。実行すべきメソッドと、その実行の対象となるオブジェクトがあれば、それでいい。
果たして、これだけでGoF本におけるCommandパターンへの要求を満たすことができるのだろうか? 即ち、要求のカプセル化、コマンドの拡張性、要求の取り消しサポートなどである。あの精緻にして複雑な機構の代替になるのだろうか。
その答えは、もちろんYESである。Cocoaのターゲット・アクション・パラダイムで、Commandパターンへの要求はすべてかなえることができる。さらに、GoF本のような無限にCommandサブクラスを作り続けることからも解放されるのである。
次回からは、ターゲット・アクション・パラダイムを、GoF本流のCommandパターンやその他のフレームワークのコマンドと比較していこう。