前回のエントリで
「外部とのインターフェースとなる部分、つまり、メソッド引数やreturn値の型、またインスタンスフィールドは厳密に抽象型を用いるべき」
と主張していたのですが、
中さんから
「なぜ抽象型を持ち上げるのかぜひ別エントリで書いてほしいと思います。
私はあくまで不要だとおもってますから。
#全然いらないという意味ではなく、必要に応じて程度でいいと思っている程度」
とリクエストされたので少し掘り下げて。
あらかじめ用語について断っておきますが、Javaのinterfaceキーワードで定義される
抽象型についてはアルファベットのinterfaceで表記します。
カタカナの「インターフェース」は一般的なプログラム用語としての接続口の意味として使います。
具象型でインターフェースを書いた場合のデメリット
具象型でインターフェースを書いた場合のデメリットは型の不整合が発生しやすく、その都度修正を迫られる点です。
Javaのコレクションフレームワークの継承階層をざっくり表現すると
- Collection
- Set (重複要素のないコレクション)
- HashSet (ハッシュを用いた実装)
- TreeSet (赤黒木によって要素の順序づけがされるSet)
- List (順序づけられたコレクション。インデックスによる要素へのアクセスが可能)
- ArrayList (1.2から追加された同期を伴わない可変長配列)
- Vector (1.0から存在する可変長配列)
- LinkedList (双方向リスト)
といった継承関係があります。(正確ではないですが、この話題ではこの程度の理解で十分でしょう)
Javaの1.1まではこれらコレクションフレームワークは整備されておらず、
可変長の配列といえばVectorのみでした。Listといった抽象的なinterfaceもなかったのです。
ですから、Javaが1.2になり、コレクションフレームワークへの移行がされるまでの間は、
次のようなインターフェースを持つメソッドがしばしば見受けられました。
public void hoge(Vector vector) { ... }
1.2以降のJavaではVectorではなくArrayListを主体として用います。
すると、この過去のメソッドhogeにArrayList型のオブジェクトを渡すことができません。
ArrayList arrayList = new ArrayList();
hoge(arrayList); // 型が合わないのでコンパイルエラー
この時期にJava界隈では可変長配列を扱う際は以下のように抽象型Listを用いるように矯正されました。
List list = new ArrayList();
このころに矯正されて抽象型で型宣言を行う癖がついている人が多いと思います。
インターフェースはできるだけ抽象型で宣言することが推奨されます。
public void hoge(List list) { ... }
このように抽象型であるListをインターフェースとして用いている場合
ArrayList arrayList = new ArrayList();
hoge(arrayList);
Vector vector = new Vector();
hoge(vector);
LinkedList linkedList = new LinkedList();
hoge(linkedList);
といったように、Listの継承型であれば何を渡しても処理できるようになります。
さらに、メソッドの作り上、ListでもSetでも一緒くたに処理できるなら
public void hoge(Collection collection) { ... }
と宣言しておけば、Setの実装も受け入れることができます。
プログラムをしているとき、つい具象型でものを考えがちですが、
そこの処理は本当にその具象型に依存するのでしょうか?
受け入れられる範囲でより抽象的な型を処理できるようにしておくと再利用性が高まります。
デメリットが小さい状況、メリットが活きない状況
元の記事にあったように「99 パーセントは ArrayList」というような状況下では
インターフェースをArrayListとしておいても困ることは少ないでしょう。
プロジェクト内でListの具象型はArrayListしか使わないというなら問題は起こらない。
また、メソッドの再利用なんて考えていなくて、新たな具象型が出てきたら
その都度その型専用に実装を作るというのであれば、それはそれで問題とはならない。
(もっともそんなコードクローンが蔓延するようなコードは保守性の点で難がありますが…)
次に、いざ型が合わなくなった場合に抽象度を高めることができる環境の場合。
最初から抽象的に考えずとも具象型で宣言しておき、
行き当たりばったりで変更をするということが可能な環境ではそれほど問題にならないことでしょう。
もし、そのライブラリを公に公開するとしましょう。
使い手は不特定多数ですが型を変更してくれと依頼があれば直すという姿勢では
ライブラリが扱いにくくて仕方がない。
いろいろな使い方がされることを見越して、ライブラリの修正なしに広く使えるように、
つまるところ再利用性が高くなるように設計するのが通常です。
ライブラリが非公開だとしても、プロジェクト内の共通部といった多数の人に利用される場合、
同様に再利用性を高めておかないと、いざ型が合わない時に手間取ります。
インターフェースの変更依頼が手間だと
ArrayList arrayList = new ArrayList(vector);
hoge(arryList);
といった可変長配列の詰め替えを行うようなコードが蔓延するかもしれません。
このあたりはインターフェースの変更のコストの大きさというパラメータで論じることができるでしょう。
まとめると、
- 具象型が単一であるならデメリットが小さい
- 再利用しない前提であるならデメリットが小さい
- インターフェースの変更コストが小さいならデメリットが小さい
ということになります。
多様な具象型を用いるし、メソッドは再利用するし、インターフェースの変更コストが高いならば、
インターフェースは抽象的にしておくにこしたことはないでしょう。
投稿日時 : 2008年3月9日 10:09