第14章 インターフェイスの活用:連載 改訂版 C#入門(1/4 ページ)
継承と並び、OOPの鍵となるインターフェイス。foreach処理の実装やソート時の並べ替え処理の実装など、この機能はコードとシステムの接点ともなる。
本記事は、(株)技術評論社が発行する書籍『新プログラミング環境 C#がわかる+使える』から許可を得て転載したものです。同書籍に関する詳しい情報については、本記事の最後に掲載しています。
インターフェイスの機能はすでに第4章で紹介済みであるが、概要を示しただけで、突っ込んだ機能や使用方法までは紹介していなかった。インターフェイスは、ただその機能を使うだけではたいした効能は期待できないように思えるかもしれないが、定義済みの種々のインターフェイスを自作クラスに実装することにより、さまざまなメリットを享受することができる。本章では、そこまで踏み込んだ、より深いインターフェイスの活用方法を解説する。
14-1 インターフェイスの効能
まずは、インターフェイスの最も基本的な効能から説明しよう。
以下のサンプル・ソースには、継承関係がまったくないクラスであるClass2とClass3がある。しかし、この2つのクラスには、同じtaskという名前のメソッドがある。ある外部のメソッドから、Class2とClass3の違いに関係なく、taskメソッドを呼び出したいとしよう。これをインターフェイスを用いて解決した例がList 14-1である。
1: using System;
2:
3: namespace Sample001
4: {
5: interface ISpecialTask
6: {
7: void task();
8: }
9: class Class2 : ISpecialTask
10: {
11: public void task()
12: {
13: Console.WriteLine("task() in Class2");
14: }
15: }
16: class Class3 : ISpecialTask
17: {
18: public void task()
19: {
20: Console.WriteLine("task() in Class3");
21: }
22: }
23: class Class1
24: {
25: static void callTask( ISpecialTask ist )
26: {
27: ist.task();
28: }
29: [STAThread]
30: static void Main(string[] args)
31: {
32: Class2 c2 = new Class2();
33: Class3 c3 = new Class3();
34: callTask( c2 );
35: callTask( c3 );
36: }
37: }
38: }
これを実行するとFig.14-1のようになる。
ここでポイントになるのは、25〜28行目のメソッドcallTaskである。このメソッドの中から、Class2とClass3に含まれるtask()を呼び出したいのである。だが、普通なら、継承関係もないクラス間で共通のメソッドを持つことはできない(ただし、厳密にいえば、すべてのクラスはSystem.Objectから派生しているものなので、継承関係がないといえば嘘になるのだが、System.Objectは勝手に書き換えられないクラスのため、この手の問題の解決には役に立たないので範囲外と考え、無関係と記述することにする)。
無関係なクラス間で共通のメソッドを持つには、インターフェイスを共有するという方法がある。List 14-1はそれを実践した例である。見て分かるとおり、Class2とClass3は、共通のインターフェイスISpecialTaskを実装している。5〜8行目で、ISpecialTaskはtask()というメソッドを実装すべきことを示している。ここではメソッドの内容は記述されない。あくまで、引数と戻り値だけが指定される。この例ではどちらもないことが明示されている。そして、9行目と16行目は、それぞれのクラスが、このインターフェイスを実装することを明示的に指定している。実装内容は、11〜14行目と18〜21行目にある。そして、25〜28行目のcallTaskメソッドは、引数としてISpecialTaskインターフェイスへの参照を取るように記述されている。ISpecialTaskを実装したクラスのインスタンスからは、必ずこのインターフェイスへの参照が取得できる。つまり、Class2のインスタンスからもClass3のインスタンスからも取得できる。そのため、34〜35行目のように、どちらのクラスのインスタンスも引数に渡すことができる(厳密にいえば、インスタンスへの参照からインターフェイスへの参照が取り出されて、それがメソッドに渡る)。
14-2 継承で実現した例
List 14-1の例は、インターフェイスを使わなくても、継承を使って実現できる。実際に継承を使った例をList 14-2に示す。
1: using System;
2:
3: namespace Sample002
4: {
5: abstract class ClassSpecialTask
6: {
7: public abstract void task();
8: }
9: class Class2 : ClassSpecialTask
10: {
11: public override void task()
12: {
13: Console.WriteLine("task() in Class2");
14: }
15: }
16: class Class3 : ClassSpecialTask
17: {
18: public override void task()
19: {
20: Console.WriteLine("task() in Class3");
21: }
22: }
23: class Class1
24: {
25: static void callTask( ClassSpecialTask ist )
26: {
27: ist.task();
28: }
29: [STAThread]
30: static void Main(string[] args)
31: {
32: Class2 c2 = new Class2();
33: Class3 c3 = new Class3();
34: callTask( c2 );
35: callTask( c3 );
36: }
37: }
38: }
これを実行するとFig.14-2のようになる。
すでに継承は説明済みなので、ソース・コードの解説は割愛する。ここで理解していただきたいことは、インターフェイスを1つだけ実装する場合は、継承を用いても、ほぼ同等の機能を実現できるということである。しかし、実装するインターフェイスが2つになると、継承で類似機能を記述することはできなくなる。List 14-3は、クラスが2個のインターフェイスを実装している例である。
1: using System;
2:
3: namespace Sample003
4: {
5: interface ISpecialTask1
6: {
7: void task1();
8: }
9: interface ISpecialTask2
10: {
11: void task2();
12: }
13: class Class2 : ISpecialTask1, ISpecialTask2
14: {
15: public void task1()
16: {
17: Console.WriteLine("task1() in Class2");
18: }
19: public void task2()
20: {
21: Console.WriteLine("task2() in Class2");
22: }
23: }
24: class Class3 : ISpecialTask1, ISpecialTask2
25: {
26: public void task1()
27: {
28: Console.WriteLine("task1() in Class3");
29: }
30: public void task2()
31: {
32: Console.WriteLine("task2() in Class3");
33: }
34: }
35: class Class1
36: {
37: static void callTask1( ISpecialTask1 ist )
38: {
39: ist.task1();
40: }
41: static void callTask2( ISpecialTask2 ist )
42: {
43: ist.task2();
44: }
45: [STAThread]
46: static void Main(string[] args)
47: {
48: Class2 c2 = new Class2();
49: Class3 c3 = new Class3();
50: callTask1( c2 );
51: callTask1( c3 );
52: callTask2( c2 );
53: callTask2( c3 );
54: }
55: }
56: }
これを実行するとFig.14-3のようになる。
インターフェイスも継承も、ソース・コード上では似たように見える。クラス宣言のクラス名の後に、コロン記号(:)を書いて、その後にインターフェイスか継承するクラス名を記述する。だが、両者の間で決定的に違うのは、インターフェイスの名前は何個でも記述できるのに対して、継承するクラス名は1個しか書けないことである。
このような制限は、技術的なものではない。事実、C++では、継承するクラス名をいくつ書いてもよい。これを多重継承という。多重継承ができれば、インターフェイスはなくてもよい。事実、C++にインターフェイスはない。にもかかわらず、C#やJavaといった比較的新しい言語で多重継承が禁止されているのは、継承が過剰に強力すぎる機能であり、容易にトラブルを引き起こすという経験による。強力すぎる継承の機能を適正な水準まで制限した結果が、継承するクラスは1個までという制約とインターフェイスの導入である。そのようなわけで、C#では、継承とインターフェイスを適切に使い分けなければプログラムが組めない。どういうときにどちらを使うのかは、.NET Frameworkのクラス・ライブラリのリファレンスなど、実例を見ているうちに何となく分かってくると思う。大ざっぱにいえば、機能性を継承するときは継承を使い、呼び出し方法を合わせるときはインターフェイスを使うのである。
Copyright© Digital Advantage Corp. All Rights Reserved.