しばしば、インターフェースについて議論に上ることがあります。インターフェースにしても、アブストラクトクラスにしても、使わなくてもプログラムは組めますし、むしろ使わないほうが余計な工数が掛からずに、素早く完成したりします。
インターフェースとは
対象のオブジェクトがどのように実装されていたとしても、実装部分を全く意識せずに使えるようにした、規格のようなものです。
現実世界には、例えば、「自動車」というインターフェースがあります。自動車には、アクセル、ブレーキ、ハンドル、シフトレバーなどが規格として定義されています。
仮に、このインターフェースが無いとどうでしょう。T社はアクセルを装備しているが、H社はアクセルではなく、スロットル。S社はブレーキ、ハンドルもなく、全てジョイスティックで操作する…、なんてことになれば、車種ごとに使い方を覚えなくてはなりません。もしそんなことが現実になれば、免許も車種ごとに必要になるでしょう。
旅客機は、機種ごとの免許が必要なようですが、プログラミングの世界では、オブジェクトをひとつ扱うだけでクラスの隅々まで知り尽くさないといけない、そんな努力はしたくありませんよね。そういった苦悩から、インターフェースという概念が生まれました。
身近なインターフェース
java.util.List
のことは皆さんよくご存知だと思いますが、これも立派なインターフェースです。
List<String> list = new ArrayList<>();
このコードに疑問を持った方も多いのではないでしょうか?
Listとして宣言しているにも関わらず、違う型のインスタンスが代入されています。これは実装クラス(具象クラス)などと言い、Listの規格に則って作られた中身のあるクラスです。
インターフェースの利便性を無視するなら、下記のコードでも動作上の違いはありません。
ArrayList<String> list = new ArrayList<>();
インターフェースの利点
さて、プログラミングを行っていると、仕様の変更により、格納する値の重複を許さないようにする必要が出てきました。add()メソッドを実行する前にcontains()メソッドを常に呼ぶようでは、呼び忘れによる余計なバグを生んでしまいかねません。そこで重複をチェックするようにした、UniqueList
という新しいリストクラスを作ることにしました。
ArrayListを継承して作ろうと考えますが、LinkedListから継承するようにと指示されました。理由は謎ですが、とにかくそうしなければなりません。
そういったシチュエーションが生じたとき List型で宣言していれば、型の変更を行う必要はありませんでした。しかし、先ほどのように、具象クラスをそのまま型として宣言していた場合は、型の変更というコストがかかります。
変数の型宣言部分だけであれば、大した変更コストではないかもしれません。しかし、これを引数に取るコードがあった場合や、値を返すコードがあった場合、途端に変更コストが跳ね上がります。
ああ、インターフェースがあればどんなに楽だっただろう…。と悔やむ前に、インターフェース指向設計を考えてみましょう。
インターフェースの設計例
インターフェースとは、可能な限り抽象的であるべきです。例えば、○○出来る、といったインターフェースは有効でしょう。
身近な例では、マルチスレッド実装を行うときに使用する、Runnable
や、シリアライズ可能なことを明示するSerializable
などです。
java.lang.Runnable
Threadクラスでは、先ほどのRunnable
を引数にとってインスタンスを生成するコンストラクタがあります。Runnableインターフェースは、run()
というメソッドが確実に存在することを保証しており、Threadクラスのstart()
メソッドにより、別スレッドで呼び出されます。
ソースコードではこのあたりはネイティブ実装となっていますが、新規スレッドでThreadクラスのrun()
メソッドが呼ばれているようです。今回は、それらの詳しい実装については割愛します。
仮にRunnableインターフェースが無いと、Threadクラスで実行すべきメソッドが特定出来ませんから、常にThreadクラスを継承してrun()
メソッドをオーバーライドしたりといった、何かと煩雑な手続きを常に踏まなければなりません。
このように考えれば、インターフェースの素晴らしさが垣間見えてきます。
抽象クラスとの使い分け
インターフェースは、メソッドの定義しか出来ないのに対して、抽象クラスでは、メソッドの動作を実装することができます。
また、抽象クラスは、ひとつの親クラスからしか継承できないのに対し、インターフェースでは、多数のインターフェースをimplementsすることができます。
大きな違いはこの二つです。
使い分けと言うと難しいと思いますが、現実世界になぞらえて考えると、インターフェースが車の規格だとするなら、抽象クラスは企業における設計図のようなものでしょう。
ある自動車をマイナーチェンジする際に、インターフェースに沿って一から設計図を作り直すよりも、既にインターフェースに従って設計されたものを継承し、それに改善点を加えていくようにしたほうが、時間もコストも抑えられます。これと同じことがプログラミングにも言えるわけです。
最後に
インターフェースは、非常に抽象的な概念であり、直感的には利点も、使う意味さえ分かりづらいものですが、一度使い方を理解してしまえば、これほど便利なものはありません。
適切に使って、高品質なコードを創出していきたいものですね。