Entry

プログラミングメモ - インターフェイスの使い方

2009年05月27日

C++ でも Java でもいいんですけど,インターフェイスを使う意味が分からないという話をよく聞きます。あたしも覚えたての頃,なんでメソッドだけ宣言したクラスが必要なのか分かりませんでした。けど,これの便利なところが分からないと,例えば,Abstract Factoy パターンや Strategy パターンの使いどころも分からない。つことで,メモがてら説明。

ちなみに,ここでは気分で Java で書いてますけど,C++ の場合は,メンバを持たないクラスのメソッドすべてを純粋仮想関数すれば,Java のインターフェイスと同じ意味になります。

まず,インターフェイスのよくある説明では,こんな風に説明されています。

そしてもう1つ、これから説明する「インターフェイス」という仕組みもあります。ただし、Javaのインターフェイスについて、「Java入門」と銘打っている書籍などでは、

Javaでは多重継承がない代わりにインターフェイスという技術が使われます

なんていうことがチャラっと書いてあります。しかし、読者は多重継承もインターフェイスも分からないから入門書を読んでいるわけで「多重継承がないのなら多重継承なんて言葉は使わなくてもいいからインターフェイスをきちんと説明してくれ」と思ってしまいます。

(snip)

「インターフェイス」はコンピュータ業界ではよく使われる言葉です。コンピュータの背面に付いている「シリアルインターフェイス」や「IEEE 1394インターフェイス」という接続端子的な意味から、キーボードやマウスといった「ヒューマンインターフェイス」、さらには疑似的なボタンなどを画面に表示する「グラフィカルユーザーインターフェイス(GUI)」まで、さまざまな場面で使われます。インターフェイス(interface)は本来「界面」という意味で、性質の異なるもの同士の接し合っている面のことをいいます。

(snip)

何となく分かってきませんか?あるクラスがどんなインターフェイスを実装しているかを見れば、そのクラスでどんなメソッドが利用可能なのか分かるのです。例えば、Windows用のパソコンとプレイステーション2は別の機械ですが、どちらにもUSBインターフェイスが付いていますので、USBキーボードを接続して操作できます。

@IT:いまから始めるJava 第14回

この後で,インターフェイスを実装(implement)するクラスは,インターフェイスで宣言しているメソッドを実装しなくてはいけない,といった実装方法に移ります。しかし,これで意味分かるでしょうか?この説明で分かったらすごいと思う。

まず,オブジェクト指向的な意味でいう「インターフェイス」というのは,USB とかいったハードウェアのインターフェイスとはあまり関係がない。いや,まったく関係ないかというと,そういうわけでもないんだけれども,関係ないと思った方が理解しやすいはずです。じゃ,どう考えればいいかというと,オブジェクト指向に言う「インターフェイス」は,クラスを「機能単位で」分類するためにある,と考えた方がいいんじゃないかと思います。

次に,「あるクラスがどんなインターフェイスを実装しているかを見れば、そのクラスでどんなメソッドが利用可能なのか分かる」というのがあって,これもよく Java の入門書なんかに書いてあります。けど,どんなメソッドが利用可能か知るだけだったら,インターフェイスなんかなくても,クラスに直接当該メソッドを書くなり,親のクラスの実装(C++ の場合は仮想関数)を見ればいいはずです。わざわざ,定義のないクラスを作る必要はない(Java の場合は多重継承できないから,少しは意味があるんだろうけど)。

インターフェイスの便利な点は,「機能の内容が分かる」とかいったような,プログラマにとっての嬉しさにあるわけじゃありません。それは,クラスを「機能単位」で分類することができる,という点にある。

ちと話が抽象的なので,具体例を挙げます。以下は,インターフェイス Helloable を実装したクラス,SomeClass1 と SomeClass2 の Java コードです。各クラスで実装されている hello() メソッドは,Test クラスから呼び出されます。

/**
 * メソッド hello() を持つインターフェイス
 */
public interface Helloable {
  public void hello();
}

/**
 * Helloable を実装するクラス(小文字で hello)
 */
public class SomeClass1 implements Helloable {
  public void hello() {
    System.out.println("hello");
  }
}

/**
 * Helloable を実装するクラス(大文字で HELLO)
 */
public class SomeClass2 implements Helloable {
  public void hello() {
    System.out.println("HELLO");
  }
}

/**
 * メインクラス
 */
public class Test {

  /**
   * 具体的にどんなクラスかは分からないけれど,
   * hello() を実装しているクラスの hello() メソッドを呼ぶ
   */
  public static void sayHello(Helloable h) {
    h.hello();
  }
  
  public static void main(String[] args) {

    /* Helloable は hello() メソッド実装する何かのクラス */
    Helloable h;

    /**
     * 引数が何もないときは SomeClass1 を作る。
     * 引数が何かあるときは SomeClass2 を作る。
     * どちらも Helloable インターフェイスを実装している。
     */
    if (args.length < 1) {
      h = new SomeClass1();
    } else {
      h = new SomeClass2();
    }

    /**
     * ここでは SomeClass1 と SomeClass2 のうち,
     * どちらが作られたか分からない(けど sayHello() を呼べる)。
     */
    sayHello(h);
  }

}

ポイントは,Test.sayHello() メソッド。この引数を見てください。

  public static void sayHello(Helloable h) {
    h.hello();
  }

引数にインターフェイスの型が指定されてますよね。つことはですよ。このメソッドは,とにかく Helloable インターフェイスが実装されているクラスのオブジェクトだったら,なんでも受け入れるメソッドだと言えます。つまり,引数になるオブジェクトが「どの型か」または「どの継承関係にあるか」を問題にしているメソッドではなくて,その機能に着目して作られたメソッドだということ。

一般に,オブジェクト指向のプログラミングでは,クラスを分類するわけですけれど,これはクラスの継承関係で分類することになっています。基本機能を持った基底クラスを拡張(継承; inherit,extend)することで,階層状の機能体系を作るわけです。しかし,特に Java のような多重継承を許さない言語の場合によく当てはまるんですけれど,継承関係は通常,樹形状の一本道なので(複数の基底クラスを持てない),きれいな継承関係を作ることは至難の業です。

例えば,上の例で,SomeClass1 と SomeClass2 が別の継承関係にあるとした場合,もしインターフェイスがないとすると,同じ機能を持つ hello() メソッドをそれぞれ別々に宣言する必要が出てきます。同じ継承関係にあれば,基底クラスに hello() メソッドを持たせられるんですけれど,継承関係が別の場合はできません。

この場合,もちろん,名前だけを見たら同じ hello() メソッドなんですけれど,SomeClass1.hello() と SomeClass2.hello() は,お互いに何の関係もないメソッドになってしまいます。もちろん,Helloable のような機能単位でまとめることもできないので,public void sayHello(Helloable h) のような,機能単位で分類されたメソッドを作ることもできません。どゆことかというと,SomeClass1.hello() 用の public void sayHello(SomeClass1 s1) と,SomeClass1.hello() 用の public void sayHello(SomeClass2 s2) を別々に定義しなくちゃいけなくなるわけです。中身はどちらも,それぞれの hello() メソッドを呼ぶわけですが。

結局,インターフェイスはどゆものなのかというと,クラスの継承関係とは「別に」,「機能単位」でクラスを分類するためにある,といったところになるんだと思います(少なくとも建前上は)。もっと言うなら,インターフェイスは,継承関係の縦糸に対する横糸として位置付けられる,と言ってもいい。継承関係とは別個の,機能を中心とした分類機能を提供している,と。C++ の場合,こゆ分類は特別なキーワード(implements)を使わなくても,実質的に純粋仮想関数と多重継承で解決できます。

つことで,「インターフェイス」の嬉しさは,ある程度継承関係を作ったクラスを作ってみないと実感できないかもしれません。クラスのまとめ方もいろいろ,つことで。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN