64.インターフェースを参照するようにせよ
インターフェースで型を宣言しといた方が柔軟な設計になる
適切なインターフェース型あるならば、引数、戻り値、変数、フィールド全てがインターフェース型で宣言されるべきである。
オブジェクトクラスを参照するのはコンストラクタにおいてのみである。
以下、Setインターフェースの実装クラスである、LinkedHashSetを例に見ていく。
// Good - uses interface as type
Set<Son> sonSet = new LinkedHashSet<>();
上記はいい例。
// Bad - uses class as type!
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
上記は悪い例。
インターフェースで宣言した例だと、以下のように変えるだけで、実装クラスが基本的にエラーなく変えられる(インターフェースで宣言しているメソッドのみ使用しているため)。
Set<Son> sonSet = new HashSet<>();
ただし、気を付けなくてはいけないのは、インターフェースでは規定されていない機能に依存したコードである場合には、実装クラスを変える影響があり得るということだ。
例えば、LinkedHashSetの並び変えのポリシーに依存していた場合には、HashSetに実装を変えた時に影響が出るだろう。
適切なインターフェースがない時
適切なインターフェースがない時は、クラスの型で宣言を行うのが正しい。
value class
例えば、StringやBigIntegerなどの値を表すクラス(value class)を考えてみる。
value classは複数の実装があることがまずない。また、おおむねfinalなクラスであり、対応するインターフェースがない。
value classは引数、変数、フィールド、戻り値の型として適している。
クラスベースのフレームワークを使っているとき
クラスベースのフレームワークを使用しており、適切な基盤の型がインターフェースでなくて、クラスであるときがある。
そういった場合も適したクラス(だいたいabstract)を継承するのが良い。
java.ioのOutputStreamなどはこれに当たる。
適したインターフェースはないが、実装クラスに適したメソッドが実装されているケース
例えば、PriorityQueueにはcomparatorメソッドがあるが、Queueインターフェースにはない。もしcomparatorメソッドに依存するプログラムを書くのであれば、PriorityQueueを参照する。ただし、このようなケースはレアである。
上記3つのケースは網羅的なものではなく、クラスで参照するなんとなくの場面について伝えたものである。
もし適したインターフェースがない場合は、目的の機能を持っている一番抽象度の高いクラスを参照すべき。