【コラム】

ダイナミックObjective-C

32 抽象クラスとクラスクラスタ

    木下誠  [2006/04/12]

    抽象クラスが無い

    Objective-Cを形容する言葉としてよく用いられるのが、シンプル、である。なるほど、シンプルというと聞こえがいい。しかし裏を返せば、機能が少ないということだ。

    例えば、Objective-Cには抽象クラスがない。正確に言えば、抽象クラスという概念はあるが、それをサポートする言語上の機構はない。Cocoaのクラス階層で、ルートクラスになるNSObjectクラスは、間違いなく抽象クラスとして設計されている。だが、プログラム上で、NSObjectのインスタンスを作ることもできる。もちろん、何の役にも立たないインスタンスだが。

    このとき、「役に立たないインスタンスならば、そのようなことができないように、言語で禁止すべきだ」という考え方もあるだろう。むしろ、近年の様々な言語の拡張の傾向を見ると、こちらが主流だろう。それに対して、Objective-Cの設計を見ていると、「そんなことはプログラマが気を付ければいい。そもそも、NSObjectのインスタンスなんて、誰も作らないだろう?」と、言っているように思えて仕方がない。この辺り、筆者には、愛すべきルーズさを感じるのだが。

    クラスクラスタ

    さて。抽象クラスはないが、それと似たような機構がCocoaでは提供されている。いや、むしろ抽象クラスよりもスマートな仕組みと言えるだろう。クラスクラスタと呼ばれる機構である。

    ここでは例として、文字列を取り扱うクラスを設計することを考えてみよう。文字列クラスはプログラミング中に頻繁に利用されるクラスであるから、特にパフォーマンスに気を付けて設計する必要があるだろう。そのために、異なる目的の文字列ごとに、特別なクラスを提供することを考える。例えば、C文字列を直接取り扱いたい場合は、その生のC文字列を保持しておくようにする。または、文字列がパスを表すならば、パス構造をキープしておきたい。

    このようなクラス階層を実現するために、まず文字列クラスの基本となるクラスNSStringを定義する。その上で、特殊用途のために、NSStringクラスのサブクラスを作成する。このとき、NSStringクラスをパブリッククラス、そのサブクラスをコンクリートクラス、と呼ぶ。そして、この集合をクラスクラスタと呼ぶのだ。

    Cocoaのクラスクラスタの面白い点は、コンクリートクラスが完全にプログラマから隠されていることだ。プログラミングでは、常にNSStringのインスタンスを作成することになる。すると、自動的に適切なコンクリートクラスが選択されるのだ。

    次のようなコードで、NSStringを作成し、実際にできあがるクラスを調べてみよう。

    NSString* str0 = @"Objective-C";
    NSString* str1 = [str0 stringByAppendingPathComponent:@"Cocoa"];

    NSLog(@"str0 is %@", NSStringFromClass([str0 class]));
    NSLog(@"str1 is %@", NSStringFromClass([str1 class]));

    str0は、リテラルによる文字列オブジェクトの作成。str1は、stringByAppendingPathComponent:という、要素を指定してパスを作成するためのメソッドを利用して作ったものだ。結果は、次のようになる。

    str0 is NSCFString
    str1 is NSPathStore2

    このように、NSStringのメソッドを使って作成したが、実際はNSCFStringとNSPathStore2のインスタンスが作成されている。これらが、NSStringのコンクリートクラスとなる。

    クラスクラスタはFactory + Template

    つまり、NSStringが抽象クラスである、と捉えることができる。コンクリートクラスを利用するときは、常にNSStringで定義されているメソッドを使うことになる。

    これに加えて、クラスクラスタの面白いところは、サブクラスの存在が完全に隠されており、その選択がNSStringに任されていることである。実際、Cocoaプログラミングの初心者は、NSCFStringやNSPathStore2の存在にまったく気付かないだろう。非常にスマートな手法といえるだろう。

    この仕組みは、デザインパターンの言葉を借りると、Factory MethodパターンとTemplate Methodパターンが合わさったもの、ということができるだろうか。面白いところは、FactoryもTemplateも、同じNSStringに見えるところである。

    NSStringの代替クラス

    もう少し、クラスクラスタの実装を詳しく調べてみよう。Objective-Cでは、通常インスタンスの作成は、allocとinitという2つのメソッドによって行われる。allocでメモリを確保し、initで初期化を行う訳だ。

    普通は、[[NSString alloc] init]というように、2つのメソッドを続けて呼ぶように書く。これを分解して、それぞれのメソッドで何が行われているか確認してみよう。

    NSString* str;
    str = [NSString alloc];
    NSLog(@"Allocated object is %@", NSStringFromClass([str class]));

    str = [str init];
      NSLog(@"Initialized object is %@", NSStringFromClass([str class]));

    allocを呼んだ直後のオブジェクトと、init後のオブジェクトのクラスを表示している。結果は、次の通りだ。

    Allocated object is NSPlaceholderString
    Initialized object is NSCFString

    allocではNSPlaceholderStringが返り、initの後ではNSCFStringとなる。このNSPlaceholderStringとは何者か?

    NSPlaceholderStringは、NSStringのサブクラスであり、クラスクラスタの一部である。このクラスの役割は、名前が示す通り、NSStringクラスの一時的な代替物であろう。つまり、allocが呼ばれた時点では、どのNSStringサブクラスを作成するか決定してないので、NSPlaceholderStringを作る。そしてinitメソッドの種類によって、サブクラスが決定してまた新たに作成されることになる。

    NSPlaceholderStringは、いわばNSStringクラスクラスタのための、Factoryクラスということになるだろう。

    新着記事

    特設サイトの情報

      人気記事

      一覧

        イチオシ記事

        新着記事

        特別企画

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