前回は、CocoaでのCommandパターンとしてターゲット・アクション・パラダイムを紹介した。単純な仕組みで、オブジェクトであるターゲットと、セレクタであるアクションを組み合わせて、1つのコマンドを表すものだ。専用のクラスを作るまでもない、というのが正直なところだろう。

今回は、これとGoF本のCommandパターンを比較しながら、議論を深めていこう。

テンプレートを使ったSimpleCommand

実はターゲット・アクションと似た構造を持つものが、GoF本でも取り上げられている。SimpleCommandと呼ばれているクラスだ。

SimpleCommandは、取り消しできず、引数を必要としない簡単なコマンドを実現するものと定義されている。C++のテンプレートを使って、次のように定義される。

List 1.

template <class Receiver>
class SimpleCommand : public Command {
public:
    typedef void (Receiver::* Action)();

    SimpleCommand(Receiver* r, Action a) : 
        _receiver(r), _action(a) {}

    virtual void Execute();
private:
    Action      _action;
    Receiver*   _receiver;
};

このクラスには、メンバ変数として_actionと_receiverがある。ちょうど、Cocoaのアクションとターゲットに相当するものだ。コンストラクタを呼ぶときに、この2つを指定してやる。これで、_actionと_receiverのペアを持つコマンドが出来上がる。

_actionの呼び出しを行うのは、Execute()関数だ。次のように実装できる。

List 2.

template <class Receiver>
void SimpleCommand<Receiver>::Execute() {
    (_receiver->*action)();
}

これが、GoF本で紹介されている、SimpleCommandクラスだ。

SimpleCommandの制約

なるほど。確かにC++のテンプレートを使って、Cocoaのターゲット・アクションと似たようなものが実現できるようだ。だが、実際の運用を想像してみると、SimpleCommandの手法には大きな制約がある事に気づく。

まず、アクションの呼び出しは、Receiverクラスのメンバ関数を呼ぶ事で行われている。つまり、SimpleCommandを使ってコマンドを受け取りたいクラスは、すべてReceiverクラスを継承しておく必要がある訳だ。これがまず、大きな制約としてプログラマにのしかかる。

さらに、アクションにも制約がある。アクションは、すべてあらかじめReceiverクラスのメンバ関数として宣言しておかなくてはいけないのだ。

これにより、SimpleCommandを使ったコマンド体系は次のようになる。まず、すべてのコマンドを受け取りたいクラスは、Receiverのサブクラスとする。さらに、すべてのコマンドから呼ばれるメンバ関数は、Receiverのメンバ関数として定義しておく訳だ。Receiverは考えうるすべてのアクションを実装した巨大なクラスとなるし、新しいアクションを追加するにはReciverの宣言を変更するので、すべてのクラスに影響が及ぶ訳だ。これは、正直ぞっとしない。

※ この部分の説明については、次回の記事で一部訂正しています。詳しくは、こちらをご覧ください。

それに対して、Cocoaのターゲット・アクションの方式を見てみよう。アクションの呼び出しには、Objective-Cの動的なメソッド呼び出しを使っている。これにより、上に述べた制約から解き放たれるのだ。まずターゲットとなるオブジェクトには、何の制約もない。特定のクラスを継承する必要はなく、すべてのオブジェクトがターゲットになれる。アクションに対する制約は、1つだけある。それはid型の引数が1つある、ということだ。それさえ守られていれば、どんなアクションでも構わない。アクションはセレクタなので、存在しないメソッドをアクションにすることもできる。

ターゲット・アクションを使ったユーザインタフェースの制御

この「存在しないアクション」を疑問に思う読者もいるかもしれない。存在しないメソッドを呼び出すコマンドが何の役に立つのか?と。実は、これすらもCocoaの特徴の1つになっている。

GUIを持つアプリケーションを作る上では、ユーザを適切に導くユーザインタフェースを作る事が大切だ。そのために有効なのが、インタフェース部品の有効/無効化である。たとえば、あるメニュー項目が現在の状況で使えない場合は、あらかじめ無効化しておく。これにより、無駄な選択をしないように、ユーザを導くことができる訳だ。

しかしそのためには、アプリケーション側でそのメニュー項目が有効なのか無効なのか、すべて判断しなくてはいけない。これはなかなか酷な作業だ。プログラマの負担が大きくなりやすい。

そこでCocoaで使われているのが、「存在しないアクション」の考え方だ。それぞれのメニュー項目には、現在のターゲットとアクションが設定されている。ターゲットは、おそらく現在選択されているオブジェクトになるだろう。このとき、そのターゲットオブジェクトが指定されているアクションを実装しているかどうか調べるのだ。実装していなければ、アクションが送れない、つまりそのコマンドには対応していないということが分かる。この情報を使って、メニュー項目の有効/無効を切り替えることができるのだ。

このように、Cocoaのユーザインタフェースの実装は、ターゲット・アクション・パラダイム、ひいてはObjective-Cの動的な特性をうまく使ってデザインされている。このあたりに、非常なエレガンスを感じる人は多いだろう。

ユーザインタフェースの有効/無効化については、Chain of Responsibilityの項でもっと突っ込んだ議論を行おう。

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

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