2010年07月10日 java言語のインターフェースの存在意義について [長年日記]
_ コーディング規約の強制を実現する
あるシステム開発現場では、作成される各クラス毎に、必ず実装する必要の有る共通メソッド(methA,methB,methC)が定められていたとする。
その場合、上記のメソッド(methA,methB,methC)をインターフェースとして宣言する。
interface Zettai_iru { void methA(); int methB(); String methC(); }
こんな感じ。
で、それぞれの開発者がクラスを設計する際には、必ず上記のインターフェース(Zettai_iru)をインプリメントする事をルールとして徹底することで、コンパイルエラーという形でメソッド(methA,methB,methC)の実装もれが未然に検知できる。
Class Hoge implements Zettai_iru { public void methA() { ... } public int methB() { ... } }
上記のクラス定義(Hoge)はmethCが実装されていないのでコンパイル時にエラーとなる。
_ メソッドの1つの引数に、実質的に複数の型(クラス)を渡すことができる
あるクラス(Boo)があって、
Class Boo { void meth_nanda() { ... } int meth_kanda() { ... } String meth_hoge() { ... } }
それが別のクラス(Foo)のメソッド(meth)の引数になっていて、methの内部ではクラスBooのメソッドの内、meth_nandaとmeth_kandaを利用したコーディングが行われているとする。
class Foo { void meth(Boo boo) { boo.meth_nanda()を使ったなんらかの処理; boo.meth_kanda()を使ったなんらかの処理; } }
メインでは以下のように呼ばれるだろう。
Class Main { public static void main(String [] args) { Foo foo = new Foo(); Boo boo = new Boo(); foo.meth(boo); } }
ここまでは、特に問題は無い。よく有る光景だと思う。
が、もしここでクラスBooによく似た別のクラス(Bar)についてもクラスFooのメソッドmethで処理したい要件が発生したとする。
クラスBarはクラスBooとよく似ているのでメソッドmeth_nanda,meth_kanda,meth_hogeは実装されている。しかし、クラスFooのメソッドmethの引数の型はBoo型と定義されているので、Bar型のオブジェクトを引数として渡すことはできない。
よって、これを解決するために、クラスFooの中にBar型のオブジェクトを引数としてメソッドmethと同様の処理を行う別のメソッド(meth_for_bar)を定義した。
class Foo { void meth(Boo boo) { boo.meth_nanda()を使ったなんらかの処理; boo.meth_kanda()を使ったなんらかの処理; } void meth_for_bar(Bar bar) { bar.meth_nanda()を使ったなんらかの処理; bar.meth_kanda()を使ったなんらかの処理; } }
これで一件落着...本当に?
ここで、クラスFooのメソッドmethとmeth_for_barは引数の型が異なるだけで、ロジックそのものはほとんどコピペされている。
今後、さらにBoo似のクラスが出て来たら同様の手法でmeth_for_xxxがどんどん増えていってしまいそうである。
また、メソッドmethの仕様変更が発生した場合にはそれぞれのmeth_for_xxx達にも同じ修正を繰り返し施す必要が出てくるのは間違い無い。
これは是非避けたい
そこでインタフェースの登場となる。
クラスFooのメソッドmeth(,meth_for_bar)の引数となるクラスで定義されたメソッドの内、メソッドmeth(,meth_for_bar)内で利用されるメソッド(meth_nanda,meth_kanda)をまとめてインタフェースとして宣言する。
interface Koreha_iru { void meth_nanda(); int meth_kanda(); }
クラスBoo,BarではインターフェースKoreha_iruをインプリメントする。
Class Boo implements Koreha_iru { public void meth_nanda() { ... } public int meth_kanda() { ... } String meth_hoge() { ... } }
Class Bar implements Koreha_iru { public void meth_nanda() { ... } public int meth_kanda() { ... } String meth_hoge() { ... } }
クラスFooのメソッドmethの引数をインターフェース(Koreha_iru)型とする。
class Foo { void meth(Koreha_iru ki) { ki.meth_nanda()を使ったなんらかの処理; ki.meth_kanda()を使ったなんらかの処理; } }
すると、メインでは以下のように呼び出すことができる。
Class Main { public static void main(String [] args) { Foo foo = new Foo(); Koreha_iru boo = new Boo(); foo.meth(boo); Koreha_iru bar = new Bar(); foo.meth(bar); } }
このように、実質的にクラスFooのメソッドmethでBoo型、Bar型両方のオブジェクトを引数として処理できることとなり、冗長なメソッドmeth_for_barは必要無くなる。
_ ある意味では多重継承
クラスはインターフェースをインプリメントすることによって、インターフェースで宣言されたメソッドの実装を強制される(実装しないとコンパイルエラーになる)。
これは見方を変えれば、あるメソッドを持つという性質のみ(これらはインターフェースで宣言されたもの)を継承していると言える。
また、クラスは複数のインターフェースをインプリメント出来るので、上述の「あるメソッドを持つという性質のみ」に関して言えば多重継承出来るのだとの解釈も大きく外れてはいないかもしれない。