【コラム】

ダイナミックObjective-C

89 デザインパターンをObjective-Cで - Chain of Responsibility (1)

    木下誠  [2007/08/30]

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

    関連したタグ

    新着記事

    特設サイトの情報

      人気記事

      一覧

        イチオシ記事

        新着記事

        特別企画

        マイナビニュースマガジン