Objective-Cの基礎 クラスの継承を理解する.
今回からは「継承」について、おそらく数回に渡ると思いますが、解説していきたいと思います。
継承とは
簡単に言うと、あるクラスを拡張して違うクラスを作ることです。
こうしてできたクラスは、独自のメソッドを持ちながらも、元のクラスのメソッドなどを継承し、それらを使うことができるのです。
例えば、「呼吸する、食べる、見る、寝る、移動する」といった特徴(メソッド)をもつ”動物”クラスがあったとします。しかし、プログラムを書いていると”動物”クラスの特徴に加え、「赤ちゃんを生む、母乳で育てる、体温を保つ」といった特徴をもった”哺乳類”クラスを作る必要が出て来ました。
この時、またゼロから「呼吸をする、食べる…」などの”動物”クラスの機能を作るのは面倒です。また、後から動物クラスの仕様が変わった場合(「嗅ぐ」を追加するなど)、”哺乳類”クラスにもその仕様を書き加えなければなりません。忘れてしまうと「嗅ぐ」機能の無い哺乳類ができてしまいます。
しかし継承という概念を使えば、このようなトラブルを防ぐことができます。クラスを作る段階で、”哺乳類”クラスは”動物”クラスを継承していますという定義さえしておけば、動物クラスの特徴はすべて哺乳類のものになり、また途中で動物クラスの仕様が変わっても反映されます。
このような機能を「継承」といい、継承の親になるクラス(ここで言う”動物”クラス)を「スーパークラス」、子になるクラス(”哺乳類”クラス)を「サブクラス」と言います。 この名称は相対的に付けられますので、例えば”哺乳類”クラスを継承した”人間”クラスを作ると、哺乳類クラスは動物クラスから見ればサブクラスですが、人間クラスから見ればスーパークラスになります。
クラスの階層構造
Objective-Cでは、クラスは必ず何かしらのクラスを継承しなくてはなりません。したがってどのクラスからでも、その親クラスをどんどん上にさかのぼっていけば、必ず「あるクラス」に行き着きます。それが「ルートクラス」です。
Cocoa環境ではそのルートクラスとして”NSObject”クラスが用意されています。このクラスはプログラム内でオブジェクトとして存在するための基礎機能が備わっており、このクラスを直接または間接的(他のクラスを継承していても、親をたどればNSObjectに行き着くので、間接的に継承していると言えます)に継承することで、全てのクラスはオブジェクトとして機能するのです。
したがって、何かクラスを作るときに、特に何のクラスも継承しなくていいと考えている時でも、最低限、オブジェクトとしての機能をもたせるためにNSObjectクラスを継承するようにしなければなりません。
メソッドの上書き
継承ではサブクラスに新しいメソッドを追加できることは説明しましたが、スーパークラスから継承されてきたメソッドを上書きすることもできます。以下にその概念を示します。
今、クラスAは変数”x”、”y”とメソッド”method1″、”method2”を持っています。そして、クラスBとクラスCは共にクラスAを継承しています。
クラスCは変数”z”とメソッド”method3”を追加しています。一方クラスBはクラスAとなんら変わりないように見えます。しかし、”method2”を上書きし、独自の新しいメソッドとして定義しています。この行為を「オーバーライド」と言います。
また、クラスBはmethod2を上書きしてしまっていますが、ある方法を使えばクラスAの”method2″にもアクセスできます。それが「super」を使った呼び出しです。この方法については後述します。
とにかく上の図のように変数、メソッドの継承が可能です。ですので、クラスBで実際にプログラムに書かなければならないのは”method2″の定義だけです。クラスCでは”z”と”method3”だけ書いてやればOKです。その他のメソッドは何も書かなくても使用可能です。
次のような例を見てみましょう。
クラスBはクラスAを継承し、method2を上書きしています。次にクラスCはクラスBを継承し、method1を上書きしています。この状態でクラスBのインスタンスに method1の実行命令が来たとすると、ここで呼ばれるのはクラスAのmethod1です。同様にmethod3の実行にもクラスAのmethod3が呼ばれます。method2の実行命令は、このクラスで新たに定義しているので、クラスBのmethod2が呼ばれます。
では、クラスCの場合はどうでしょうか。method1の実行は、上書きをしているクラスCのmethod1が実行されます。method3が呼ばれたときは、クラスBにもその定義は無いので、クラスAのmethod3が実行されます。そして、method2が呼ばれたときは、自分のクラスにはなく、クラスBで再定義されてますので、クラスBのmethod2が実行されます。
このようにインスタンスでメソッドにメッセージが送られたときは、まず自分のクラスを探し、そのメソッドがあればそれを実行します。なければもう一つ上の階層のクラスでそのメソッドを探し、実行します。それでもなければもう一つ上、という優先順位でメソッドは実行されます。
継承の定義方法
では実際に新たにクラスを定義する際、どのようにプログラムで継承関係を定義するか見て行きましょう。
継承関係の宣言はインターフェース部で行います。実は以前の投稿で出てきていたのですが、きちんと説明していませんでした。その時にNSObjectと書かれていた部分がスーパークラスの宣言をしていた部分です。わかりやすく書くと以下の様な構造で書くことができます。
@interface クラス名 : スーパークラス名 { インスタンス変数宣言 } メソッド宣言など @end
あなたが今新しく作るクラスが「クラス名」で、継承するクラス名が「スーパークラス名」です。何も思い浮かばないときは”NSObject”と書いておきましょう。
例として、先ほどの図でのクラスBのインターフェース部を書いてみましょう。
#import "ClassA.h" @interface ClassB : ClassA { } -(void) method2; //Override @end
このようになります。まず、クラスAのヘッダーファイルをインポートします。これをしなければ、クラスBからはクラスAにどんな機能が備わっているかわかりません。
インスタンス変数は新たに何も追加していないので、インターフェース部に書かなくて大丈夫です。このままでクラスAのインスタンス変数xもyも使うことができます。メソッドはmetho2のみ上書きしているので、メソッド宣言部で宣言しておきます。もちろん実装部ではこの実装を書いてください。また、このメソッドが上書きであることはコメントなどで明示しておくようにしましょう。
このようにObjective-Cでは継承関係をクラスに持たすことができます。
基本的な継承の概念は説明できたので、次回は先程少し出てきたスーパークラスのメソッドの呼び出しについて書きたいと思います。