実は、1つ手前のサンプル・ソース(抽象クラス)と、ほとんど同じことができる方法がもう1つある。それは、クラスとは異なるインターフェイスという機能を利用する方法である。インターフェイスは中身のないメソッド定義などを含むもので、機能的には抽象クラスと似ている。しかし、抽象クラスは、あくまで中身のないメソッドを含むクラスであって、クラスそのものに変わりはない。つまり、クラスに許されるあらゆる記述が可能である。その中には、中身のあるメソッドも含まれる。それに対して、インターフェイスは、いっさい中身を持たず、メソッドなどの宣言だけを持つ。それだけなら、中身のまったくない抽象クラスを用いればインターフェイスの代用になりそうなものだが、実際はそういうわけにはいかない。なぜなら、継承する際には、スーパークラスとなれるクラスは1個に限られるが、インターフェイスはいくつでも受け入れることができるからだ。いくつもの機能や役割を1個のクラスに持たせるためには、インターフェイスは不可欠な存在なのである。
それでは、インターフェイスを用いて書き直した例をList 4-5に示す。
1: using System;
2:
3: namespace Sample005
4: {
5: public interface IPerson
6: {
7: string getName();
8: }
9:
10: public class Taro : IPerson
11: {
12: public string getName()
13: {
14: return "私の名前は太郎です。";
15: }
16: }
17:
18: class Class1
19: {
20: [STAThread]
21: static void Main(string[] args)
22: {
23: // 次の行はコンパイル・エラーになる
24: // 抽象クラスまたはインターフェイス 'Sample005.IPerson' のインスタンスを作成できません。
25: // IPerson person = new IPerson();
26: Taro taro = new Taro();
27: Console.WriteLine( taro.getName() );
28: IPerson someone = new Taro();
29: Console.WriteLine( someone.getName() );
30: }
31: }
32: }
このソース・コードのポイントは、5行目のインターフェイスの宣言と、12行目のメソッド宣言である。インターフェイスの宣言はclassキーワードの代わりにinterfaceキーワードを用いて行う。しかし、その際に、abstractキーワードなどは必要ない。また7行目のメソッドの宣言にも、abstractキーワードは必要ない。インターフェイスであるというだけで、実行すべき中身がないのは明らかだからだ。また、12行目でも、特にoverrideのようなキーワードは必要とされていない。インターフェイスに含まれるメソッドを実装するのは、それ以外に選択の余地がなく、キーワードを添えて区別する意味がないからだ。もし、ここでgetNameメソッドがなければ、エラーになりコンパイルできない。
当然のことながら、インターフェイスはインスタンスを生成することはできない。そのため、25行目のコメントに書かれたようなコードは書き込んでもコンパイル・エラーになる。
このソース・コードでは、意図的に1つだけキーワード名を変えてある。ほかのサンプル・ソースでは、Personと記述していたものを、この例だけはIPersonとしている。これは、インターフェイス名には先頭に「I」の1文字を添えるという慣習があるためだ。けっして言語仕様の一部ではないので、付けなくてもコンパイルは通る。しかし、プログラムの分かりやすさという観点からは、この慣習に合わせるのがよいだろう。
これを実行した結果はFig.4-6のようになる。
継承は非常に強力な機能である。小さな共通機能のクラスを作り、それを継承して、さまざまな場面で必要とされるさまざまなクラスを作り出すことができる。しかし、その強力さは、使い方を間違えるとプログラマー自身に刃を向けかねない危険もはらんでいる。継承を何回も繰り返したソース・コードは、見通しが悪くなるからである。例えば、あるクラスのインスタンスのメソッドを呼び出すソース・コードを見る場合を考えてみよう。そのメソッド呼び出しによって実際に呼び出されるメソッドがどのクラスに属しているか調べるとき、継承がなければ一瞬で分かる。しかし、複雑な継承が行われていると手間がかかる恐れがある。
そのことから導き出される経験的な結論は、継承の活用はプログラムをエレガントにするが、継承の使い過ぎはトラブルのもとということである。例えば、本章のサンプル・ソースには、すべての人に共通なPersonクラスと、特定の人物の持つ機能を実現するTaroクラスがあった。もし、プログラム中に出現する人物がTaroさんただ1人なら、PersonクラスをTaroクラスとは別に作る意義はなく、Taroクラスだけで行うほうがよい。もう1つの例を挙げよう。Personクラスだけでは不十分だと考え、Personクラスを継承して、MaleクラスとFemaleクラスを作り、Maleクラスを継承してTaroクラスを作るという方法はどうだろうか? もし、男と女で何か明確な機能の違いが要求されていれば、このようなクラス構成には意味がある。しかし、このような考え方でどんどん人間を分類して細分化されたクラスを作ることがよいかというと、必ずしもそうとはいえない。継承を十数回も繰り返して生まれたクラスは、動作を理解するためにソース上に分散した数十個のクラスのソースを確認しなければならない。これでは、ソースを分かりやすくするために継承を使ったはずが、かえってソースを分かりにくくしてしまうことになる。何事も、過ぎたるは及ばざるが如し、ということだ。
ここで説明した内容は、継承にまつわる問題のほんの一部にすぎない。継承を使うか使わざるかは非常に奥の深い問題で、人によって解釈も変わる。本連載では、これ以上深く踏み込まないことにして、説明はここまでとする。
クラスの特徴の1つが継承によって機能を付け加えることができることだが、場合によっては継承されると具合が悪いことがある。そのような場合には、sealedキーワードを付け加えて、継承しようとするとエラーが起きるように記述することができる。
1: // 注意:このソースはビルドがエラーになるサンプルである
2: using System;
3:
4: namespace Sample006
5: {
6: sealed class A
7: {
8: }
9: class B : A // 'Sample006.B' : シール クラス 'Sample006.A' から継承することはできません。
10: {
11: }
12: }
自分でクラスをシールする機会は少ないかもしれないが、クラス・ライブラリの中にはしばしばシールされたクラスが存在するので、知識として頭に入れておく価値があるだろう。
『新プログラミング環境 C#がわかる+使える』
本記事は、(株)技術評論社が発行する書籍『新プログラミング環境 C#がわかる+使える』から許可を得て一部分を転載したものです。
【本連載と書籍の関係について 】
この書籍は、本フォーラムで連載した「C#入門」を大幅に加筆修正し、発行されたものです。連載時はベータ版のVS.NETをベースとしていましたが、書籍ではVS.NET製品版を使ってプログラムの検証などが実施されています。技術評論社、および著者である川俣晶氏のご好意により、書籍の内容を本フォーラムの連載記事として掲載させていただけることになりました。
→技術評論社の解説ページ
ご注文はこちらから
Copyright© Digital Advantage Corp. All Rights Reserved.