Objective-Cのポリモーフィズムを使いこなす.
オブジェクトの型、メッセージの送信。こういったものを大かた理解できてきたので、今回はポリモーフィズムについて解説します。このへんからObjective-Cらしさが出てきて、面白くなってくるところです。
id型とは
ポリモーフィズムの本題に入る前に、この話をしておかなければなりません。そんな難しい話ではないですが。説明するのを忘れていました。
Objective-Cのプログラムの中でよく見る「id」というキーワード。これもクラスなどと同様、ひとつの型です。
しかし、とっても融通の効くやつです。どんな型のオブジェクトでも格納できる入れ物になってます。
ClassA* a = [[ClassA alloc] init]; ClassB* b = [[ClassA alloc] init]; // ←エラーになる
例えば上記のようにクラスA型の変数aを作るときは、クラスA型でメモリ確保をし、インスタンス化します。3行目のようにクラスB型で宣言したオブジェクトにクラスAのインスタンスを入れようとしてもエラーになります。
では次の場合はどうでしょうか。
id a = [[ClassA alloc] init]; id b = [[ClassA alloc] init];
この場合はエラーなくコンパイルできます(もちろんbはクラスAのインスタンスですが)。このように「id」はどんなタイプにもなり得る汎用的な型なのです。「オブジェクト型」とでも言っておけばいいでしょうか。
ポリモーフィズムの概念
このidがどんなクラスのオブジェクトにもなり得るという特徴を活かせば、非常に柔軟で本質的なプログラムを書くことができるようになります。
以下の例を見てみましょう。
/*クラスAの定義*/ @interface ClassA : NSObject -(void) sayClassName; @end @implementation -(void) sayClassName { NSLog(@"My class is A."); } @end /*クラスBの定義*/ @interface ClassB : NSObject -(void) sayClassName; @end @implementation -(void) sayClassName { NSLog(@"My class is B."); } @end /*メインプログラム*/ int main(void) { id obj; int n = rand() % 3; // 0~2の数値をランダムで生成 NSLog(@"n is %d.", n); switch(n) { case 0: obj = [[ClassA alloc] init]; break; case 1: obj = [[ClassB alloc] init]; break; case 2: obj = [[NSObject alloc] init]; break; } [obj sayClassName]; return 0; }
まずはじめに、クラスAとクラスBの定義を行なっています。そして、どちらも”sayClassName”というメソッドを持っています。しかし、出力の内容が異なっています。
メイン関数では、まずobjの型をidで宣言し、nに0〜2の無作為整数を格納しています。そして、switch文でnの内容を参照し、数値によって、作るオブジェクトの型を変えています。最後に、sayClassNameメソッドへメッセージを送り出力しています。
コンパイルしてみると問題なくコンパイルできるはずです。
さて、実行してみるとどうでしょうか。
nの値が0の時は「My class is A.」と、1の時は「My class is B.」と表示されます。しかし、2の時はエラーになって落ちます。
これは、NSObjectクラスに”sayClassName”というメソッドが無いからです。
Objective-Cではこのように、コンパイル時に変数の型や、メソッドの有無がチェックされません。オブジェクトがメソッドに対応できるかどうかは、実行時、実際にメッセージが送られた時に決定されるのです。
このような機能を動的結合と呼び、同じメッセージを送っても、オブジェクトの型によって違う処理が実行される性質をポリモーフィズムと呼びます。
この機能は、実行時に思わぬ動作不良を起こす原因にもなりますが、使い方によっては、柔軟なプログラムを作る手助けもしてくれます。
ポリモーフィズムが有効な例
ゲームのキャラクタを考えてみましょう。人間のキャラ以外に動物や鳥といったキャラがいるとします。どれも前へ進む機能があるはずです。
しかし、その動作は二足歩行であったり、飛行であったりとそれぞれのキャラで違う動作であるはずです。
動かしたいオブジェクトを取得して、前進のメッセージを送ろうとすると、ポリモーフィズムが使えない場合、まずオブジェクトの型を判断し、その型に合ったメソッドを呼び出さなければなりません。
switch(character->type) { case: Human: walk(); break: case: Animal: animalWalk(); break: case: Bird: fly(); break: }
一方、ポリモーフィズムが使える場合、前進するというセレクタ名さえ統一しておけば、とてもシンプルにかつ本質的にプログラムが書けます。前進するメソッドを”moveForward”という名前にしておきましょう。すると以下の文で前進機能が実現できます。
[ character moveForward ];
このように書いておけば、characterの型によって、自動でそのキャラのクラスの前進メソッドが呼び出されます。
これは新しいキャラを追加するときにも大変有効です。
今、ヘビクラスのキャラを追加するとします。ここで気をつけなければならないのは、ただ前進するメソッド名を他のものと同じ”moveForward”にしておくことだけです。
そのメソッドさえクラスに保持しながら作ればあとは「 [ character moveForward]; 」文が自動で型を判定し呼び出してくれます。
このようにid型とポリモーフィズムを使って柔軟なプログラムを書けることがObjective-Cの魅力でもあり、難しい部分でもあります。そして、これを使いこなせるかどうかが、プログラマの腕の見せ所でもあるわけです。