Interface


POINT インターフェイスは単なるプログラム言語の機能というだけでなく、 システム側に用意されたさまざまな便利な機能にアクセスするための入り口という意味合いも持つ。 いろいろなインターフェイスを自作クラスに実装することにより、システムが用意したさまざまな機能を利用したり、 自由自在にコントロールしたりできる。 そういう意味で、インターフェイスを自分で宣言するよりも、 まずは既存のインターフェイスの活用から始めるとよいだろう。


インターフェイスの効能


POINT Interface と Inheritance の違いは, Interface は複数可能だが, Inheritance はひとつのみ. その制約が生まれた背景は, MultiInheritance が Trouble をひきおこしやすいため. ので Inheritance はひとつ.Interface は複数という Rule にした. 逆に C++ ではこの Guideline にあわせよう. ^ ^/ 使いどころとしては, Inheritance : 機能性を継承. Interface : クラスの種類に関係なく呼び出し方法を合わせる.  初心者の場合、自分でインターフェイスを定義する機会は多くないかも。 しかし、システム側で定義しているいくつかのインターフェイスを自作クラスに実装すると、 いろいろと便利な機能が有効になる [ システムが定義するインターフェイス ]を活用すると何ができるのかを紹介しよう。 -> 自分が System になって Interface を定義することも可能. ^ ^/ インターフェイスの最も基本的な効能 継承関係がまったくないクラスであるClass2とClass3を定義している。 この2つのクラスには、同じtaskという名前のメソッドがある。 ある外部のメソッドから、Class2とClass3の違いに関係なく、taskメソッドを呼び出したいとしよう。 // インターフェイスを用いて解決した例 using System; namespace ConsoleApplication93 { // Interface を作成. ( Access ないのね. ^ ^/ ) interface ISpecialTask { void task(); } class Class3 : ISpecialTask { public void task() { Console.WriteLine("task() in Class3"); } } class Class1 { static void callTask( ISpecialTask ist ) { ist.task(); } static void Main(string[] args) { Class2 c2 = new Class2(); Class3 c3 = new Class3(); callTask( c2 ); callTask( c3 ); } } } インターフェイスの最も基本的な効能を示すサンプル・プログラム1 異なるクラスにある同じ名前のメソッドを、クラスの違いとは無関係に呼び出す。 通常は継承関係もないクラス間で、共通のメソッドを持つことはできない。 無関係なクラス間で共通のメソッドを持つには、インターフェイスを共有する、 という方法がある。 Class2とClass3は、共通のインターフェイス ISpecialTask を実装している。 ISpecialTaskはtask()というメソッドを実装すべきことを示す。 ここではメソッドの内容は記述されない。あくまで、引数と戻り値だけが指定される。 それぞれのクラスが、このインターフェイスを実装することを明示的に指定している。 callTaskメソッドは、引数としてISpecialTaskインターフェイスへの参照を取るように記述されている。 ISpecialTaskを実装したクラスのインスタンスからは、必ずこのインターフェイスへの参照を取得できる。 つまり、Class2のインスタンスからも、Class3のインスタンスからも取得できることになる。 そのため、33〜34行目のように、どちらのクラスのインスタンスも、引数に渡すことができる (厳密にいえば、インスタンスへの参照から、インターフェイスへの参照が取り出されて それがメソッドに渡る)。  上の例は、インターフェイスを使わなくても、継承を使って実現できる。 using System; namespace ConsoleApplication94 { // 抽象クラスをつくる. abstract class ClassSpecialTask { public abstract void task(); } class Class2 : ClassSpecialTask { public override void task() { Console.WriteLine("task() in Class2"); } } class Class3 : ClassSpecialTask { public override void task() { Console.WriteLine("task() in Class3"); } } class Class1 { static void callTask( ClassSpecialTask ist ) { ist.task(); } static void Main(string[] args) { Class2 c2 = new Class2(); Class3 c3 = new Class3(); callTask( c2 ); callTask( c3 ); } } } POINT インターフェイスを1つだけ実装する場合は 継承を用いても、ほぼ同等の機能を実現できる。 しかし 実装するインターフェイスが2つになると、継承で類似機能を記述することはできなくなる。 以下は、クラスが2個のインターフェイスを実装している例である。 using System; namespace ConsoleApplication95 { interface ISpecialTask1 { void task1(); } interface ISpecialTask2 { void task2(); } class Class2 : ISpecialTask1, ISpecialTask2 { public void task1() { Console.WriteLine("task1() in Class2"); } public void task2() { Console.WriteLine("task2() in Class2"); } } class Class3 : ISpecialTask1, ISpecialTask2 { public void task1() { Console.WriteLine("task1() in Class3"); } public void task2() { Console.WriteLine("task2() in Class3"); } } class Class1 { static void callTask1( ISpecialTask1 ist ) { ist.task1(); } static void callTask2( ISpecialTask2 ist ) { ist.task2(); } static void Main(string[] args) { Class2 c2 = new Class2(); Class3 c3 = new Class3(); callTask1( c2 ); callTask1( c3 ); callTask2( c2 ); callTask2( c3 ); } } } 継承では類似の機能を記述することはできない。 2つのクラスにある2つのメソッドが、それぞれ順に呼び出される。  インターフェイスも継承も、ソースコード上では同じように見える。 クラス宣言のクラス名の後に、コロン記号(:)を書いて、 その後にインターフェイスか継承するクラス名を記述する。 だが、両者の間で決定的に違うのは、インターフェイスの名前は何個でも記述できるのに対して、 継承するクラス名は1個しか記述できないこと。  このような制限は、技術的なものではない。 事実C++では、継承するクラス名をいくつ書いてもよい。 これを多重継承という。多重継承ができれば、インターフェイスはなくてもよい。 事実、C++ にインターフェイスはない。 にもかかわらず、C#やJavaといった比較的新しい言語で多重継承が禁止されているのは、 継承が過剰に強力すぎる機能であり、トラブルを引き起こしやすいという経験による。 強力すぎる継承の機能を、適正な水準まで制限した結果が、継承するクラスは1個までという制約と、 インターフェイスの導入である。 POINT 機能性を継承するときは継承を使う。 呼び出し方法を合わせるときはインターフェイスを使う


インターフェイスの継承


POINT Interface は 継承できる. ie. Grouping できるので, 基本的な機能は IBase にまとめると便利 ! ^ ^/ Interface は Property, Indexer にも利用可能. インターフェイスはクラスのように継承できる。 using System; namespace ConsoleApplication96 { // Interface // IBaseというインターフェイスを定義する。 interface IBase { void task1(); } // IDerivedというインターフェイスを、IBaseを継承して定義する。 // IDerivedには、IBaseで定義されたメソッドと、 // IDerived自身で定義されたメソッドが含まれるのである。 interface IDerived : IBase { void task2(); } class Class1 : IDerived { // ふたつ実装してね. ^ ^/ public void task1() { Console.WriteLine("task1() called"); } public void task2() { Console.WriteLine("task2() called"); } static void Main(string[] args) { Class1 c1 = new Class1(); c1.task1(); c1.task2(); } } } インターフェイスを実装しているクラスでは、継承されたメソッドも実装する必要がある。 インターフェイスの継承は、大規模なインターフェイスを定義する場合に便利になる。 基本的なインターフェイスを定義してから、 それらを集めて大きなインターフェイスを定義できるからである。 そのため、IDerivedを実装するClass1では、15〜18行目と19〜22行目で、 IBaseで定義されたメソッドと、IDerivedで定義されたメソッドの両方を実装している。 記述の方法はクラスの継承と同じ インターフェイスを実装しているクラスでは、継承されたメソッドも実装する必要がある。 インターフェイスの継承は、大規模なインターフェイスを定義する場合に便利になる。 IBaseというインターフェイスを定義している。 次に IDerivedというインターフェイスを、IBaseを継承して定義している。 つまり、IDerivedには、IBaseで定義されたメソッドと、IDerived自身で定義されたメソッドが含まれるのである そのため IDerivedを実装するClass1では、 IBaseで定義されたメソッドと、IDerivedで定義されたメソッドの両方を実装している。  この機能は大規模なインターフェイスを定義する場合に便利だといえる。 基本的なインターフェイスを定義してから それらを集めて大きなインターフェイスを定義できるから。 プロパティ、インデクサ、イベントとインターフェイス  メソッドだけでなく、プロパティ、インデクサ、イベントもインターフェイスに記述できる。 using System; namespace ConsoleApplication97 { public delegate void SampleEvent(object sender, EventArgs e); // インターフェイスの定義部分 // 6行目でinterfaceの前にpublicが付いているのは、 // 5行目のdelegateの宣言に付くpublicと水準を合わせるためのもの // 水準を合わせないとエラーになる。 public interface ISample { // 普通はgetやsetの後ろに中括弧を書いて // 処理内容を書くのだが、ここはインターフェイス内なので、実装は書かない。 // ここでgetだけ、あるいはsetだけ書けば、読み出し専用、あるいは書き込み専用のプロパティを強制できる。 int SampleProperty { get; set; } // インデクサ getとsetに関しては、プロパティと同じ string this[int index] { get; set; } // イベントの宣言 // インターフェイス内のイベントは、当然、単なる宣言にすぎないので、実装しなければ使えない。 event SampleEvent sampleEvent; } class Class1 : ISample { private int sample = 0; // プロパティ public int SampleProperty { get { return sample; } set { sample = value; } } private string [] ar = new string[3]; public string this[int index] { get { return ar[index]; } set { ar[index] = value; } } public event SampleEvent sampleEvent; public void handler(object sender, EventArgs e) { ISample isample = (ISample)sender; Console.WriteLine( isample.SampleProperty ); Console.WriteLine( isample[0] ); Console.WriteLine( isample[1] ); Console.WriteLine( isample[2] ); } public void invokeEvent() { // イベントハンドラで、プロパティおよび配列にセットした値を表示している。 sampleEvent( this, EventArgs.Empty ); } static void Main(string[] args) { Class1 c1 = new Class1(); c1.SampleProperty = 123; c1[0] = "ABC"; c1[1] = "DEF"; c1[2] = "GHI"; c1.sampleEvent += new SampleEvent(c1.handler); c1.invokeEvent(); } } }


同名のメソッドを持つインターフェイス


 あるクラスで2つのインターフェイスを実装しようとしたが、 偶然にも同じ名前のメソッドが定義されていたらどうなるだろうか。
{ interface ISample1 { void task(); } // あ ! 名前かぶっちゃった. ! interface ISample2 { void task(); } class Class2 : ISample1 { public void task() { Console.WriteLine("task() in class2 called"); } } class Class3 : ISample1, ISample2 { // どちらを実装するか明示する. void ISample1.task() { Console.WriteLine("ISample1.task() in class3 called"); } void ISample2.task() { Console.WriteLine("ISample2.task() in class3 called"); } } class Class1 { static void Main(string[] args) { Class2 c2 = new Class2(); // 同一名では次のような単純な呼び出しではうまくいかない. c2.task(); Class3 c3 = new Class3(); //c3.task(); // 'ConsoleApplication98.Class3' に 'task' の定義がありません。 // cast してから call() すれば OK. ISample1 i1 = (ISample1)c3; i1.task(); ISample2 i2 = (ISample2)c3; i2.task(); } } }
Class3(20行目)では、メソッドの定義をISample1.task(22行目)や ISample2.task(26行目)のように、実装するインターフェイスを明示して記述する必要がある。 オブジェクトをインターフェイスにキャストしてからメソッドを呼び出す必要がある。 POINT この2つのインターフェイスを 20 行目のように1つのクラスで実装しようとすると、 戻り値も引数も共通なので、そのままでは区別することができない。 そこで、どのインターフェイス由来のメソッドを実装しようとしているのか、 インターフェイス名を明示して記述する。 22〜25行目はtaskではなく、ISample1.task と明示的に書くことで、 インターフェイス ISample1 の task を実装していることを明示する。 インターフェイス ISample2 の task を実装している。 まずインターフェイスにキャストしてから呼び出せば、曖昧さはなくなる。 クラスClass3のインスタンスを、インターフェイスISample1にキャストしている。 キャスト結果に対して40行目ではtaskメソッドを呼び出しているが、このコードは間違いなく、 インターフェイスISample1のtaskメソッドを呼び出す。 自作クラスを foreach 可能にする POINT System が用意した Interface を実装することで, 各種サービスをうけることが可能. POINT システムで定義されたインターフェイスを自作クラスに実装することで便利機能を使うことができる。 まず最初は、自作クラスを foreach 可能にする方法からだ。  C# の foreach 文は、VisualBasic などから取り入れた構文と思われるが、配列などを渡すだけで、 自動的にすべての要素を繰り返してくれる便利なもの。 これは、.NET Frameworkに含まれる多くのクラスで利用できるが、 なぜこれほど多くのクラスで使えるかというと、 繰り返しのメカニズムを、インターフェイスを経由して実現しているからだ。 つまり、インターフェイスさえ実装されていれば、どんなクラスであろうと、 foreach で利用できる。 using System.Collections; { // IEnumerator Interface を実装する. // 要は Interface class ClassMyEnumerator : IEnumerator { private int pointer; string [] target; object IEnumerator.Current { get { return target[pointer]; } } bool IEnumerator.MoveNext() { if( pointer >= target.GetUpperBound(0) ) return false; pointer++; return true; } void IEnumerator.Reset() { pointer = target.GetLowerBound(0)-1; } // Ctor public ClassMyEnumerator( string [] array ) { target = array; pointer = target.GetLowerBound(0)-1; } } // IEnumerable Interface を実装することで, foreach 可能. class ClassSample : IEnumerable { string [] ar = { "ABC", "DEF", "GHI" }; IEnumerator IEnumerable.GetEnumerator() { // return new ClassMyEnumerator(ar); } } class Class1 { static void Main(string[] args) { ClassSample c = new ClassSample(); foreach( string s in c ) { Console.WriteLine(s); } } } } IEnumerableとIEnumeratorの2つのインターフェイスを使用する。 クラス内であらかじめ定義されている文字列を foreach文を使用して順に取り出すことができる。  ここでは2つのインターフェイスが登場している。 IEnumerable で、これを実装したクラスは、foreach 可能と見なされる。 IEnumerator で、実際に要素を1個ずつ列挙する機能を提供する。 IEnumerableは、あなたの自作クラスに実装し、 IEnumeratorはあなたの自作クラスを列挙するクラスに実装するものである。 ここでは、3つの文字列を内部に持つクラスが定義されている。 実際には32行目の配列はそのまま foreach 可能なので いちいちインターフェイスを実装しなくてもよいのだが、 foreach 可能にするには、IEnumerableを実装するのだが、 これには、GetEnumeratorというメソッドだけが宣言されている。 そこで、33〜36行目で、そのメソッドを実装している。 やるべきことは、列挙を実際に行うクラスのインスタンスを作成して、それを返してやるだけだ。  実際に列挙するのは、ClassMyEnumeratorクラス IEnumeratorに含まれる Currentプロパティ、 MoveNextメソッド、 Resetメソッド を実装すればよい。 Resetメソッドは、列挙の初期化をする。 列挙は、最初のアイテムの1個手前の状態から始まるので、22行目の最後でマイナス1をしている。 MoveNext は、次の要素に1個進める機能を持つ。 アクセスする対象を示す値をプラス1するのが基本動作だ。 そして、いよいよデータそのものにアクセスするのが、Currentプロパティだ。 これは読み出し専用で、MoveNextメソッドで進めた場所にある内容を返す。 なお、GetUpperBoundメソッドは配列の上限の添え字を返すメソッド、 GetLowerBoundは同様に下限を返すメソッドである。  以上のような処理が記述されていれば、自作クラスのインスタンスを、 foreach 文で使うことができる。


インターフェイスの基本


__SYNTAX__ [attributes] [modifiers] interface identifier [:base-list] {interface-body}[;] DESC そもそも 型安全のためにある仕組み. 1. POINT 1. 機能( Signature )は必要だけど, 中身は不要の場合に作成する. 1. interface と abstract は異なる. 1. C++ では, 同一の Signature であっても問題ないらしい. class IA { public: virtual void func( int ) = 0; }; class IB { public: virtual void func( int ) = 0; }; class Concrete : public IA, public IB { public: void func( int a ) { dpi( a ); } }; // Concrete を作成して, funcA, funcB に渡すのは問題なし. // 要は, c が生成できたということは, すべての Method の実装が終了すみであり, // interface をわたすことで問題がないはず. { Concrete c; funcA( c ); funcB( c ); } 多重継承ではなくインターフェイスという技術 抽象的にクラスの動作「ビヘイビア」を宣言します. ( 要はこんな機能が必要だよねを宣言. ) クラスは1つの基底クラスしかもつことができないが、インターフェイスはいくつも実装できる。 物理的な動作を見ると「実装する」という表現が適確なので この場では「実装する」という表現を使う。 POINT インターフェイスの役割は、クラスに対する契約。 ビヘイビアを必ず実装しなければならない。 プロジェクトの設計者がインターフェイスを設計して その仕様をアプリケーション設計者に指示すれば 異なるコンポーネント間でも、インターフェイスを通じて同じ操作を保証できる。 Standard 型は、実装はともかく WriteName() というシグネチャを保証しているのです Standard を実装するクラスは Standard 形であるとも考えられ このプログラムのように異なるクラスを同一の型として扱うことができる。 POINT インターフェイスは常に公開される必要があるため メンバはアクセス可能性を指定されない。 インターフェイスの結合 インターフェイスは、他のインターフェイスを含むことで結合できる public interface IBreath { int BreathCount { get ; } void Touch() ; } // もう一例.   public interface IPerson   {   string getName(); // access 修飾子は不要らしい.   }