web-dev-qa-db-ja.com

Javaオブジェクトを実装(HashMap)ではなくインターフェイス(例:Map)を使用して定義する理由)

ほとんどのJavaコードでは、人々はJavaオブジェクトを次のように宣言します:

Map<String, String> hashMap = new HashMap<>();
List<String> list = new ArrayList<>();

の代わりに:

HashMap<String, String> hashMap = new HashMap<>();
ArrayList<String> list = new ArrayList<>();

実際に使用される実装ではなく、インターフェースを使用してJavaオブジェクトを定義するための設定があるのはなぜですか?

17
Suman

その理由は、これらのインターフェイスの実装は通常、それらを処理するときには関係がないためです。したがって、呼び出し元にHashMapをメソッドに渡すように義務付ける場合、基本的にどの実装を使用するかを義務付けます。したがって、一般的な規則として、実際の実装ではなくそのインターフェイスを処理し、HashMapを代わりに使用する必要があると判断したときにLinkedHashMapを使用してすべてのメソッドシグネチャを変更する必要が生じる可能性のある苦痛を回避する必要があります。

実装に関連する場合、これには例外があると言う必要があります。順序が重要なときにマップが必要な場合は、TreeMapまたはLinkedHashMapを渡すか、特定の実装を指定しないSortedMapを渡す必要があります。これにより、呼び出し元は特定のタイプのMapの実装を必ず渡す必要があり、その順序isが重要であることを強く示唆しています。つまり、SortedMapをオーバーライドして、並べ替えられていないものを渡すことはできますか?はい、もちろん、結果として悪いことが起こることを期待しています。

ただし、ベストプラクティスでは、それが重要でない場合は特定の実装を使用しないように指示しています。これは一般的に当てはまります。 Dogから派生したCatAnimalを扱う場合、継承を最大限に活用するには、通常、DogまたはCatに固有のメソッドを使用しないようにする必要があります。むしろ、DogまたはCatのすべてのメソッドは、Animalのメソッドをオーバーライドする必要があり、長期的には問題を解決できます。

26
Neil

Laymanの言葉で:

同じ理由で、電気機器メーカーはケーブルを単に剥がす代わりに電気プラグを使用して製品を製造し、家には壁から突き出ているケーブルを剥がす代わりにコンセントが付いています。

代わりに標準プラグを使用することにより、家の周りの互換性のあるプラグに同じアプライアンスを接続できます。

コンセントの観点からは、テレビやステレオを接続するかどうかは関係ありません。

これにより、アプライアンスとソケットの両方がより便利になります。

Mapを引数として受け取るメソッドを例にとります。

メソッドは、Mapのサブクラスである限り、HashMapやLinkedHashMapを渡しても機能します。

それは Liskov置換原理 です。

あなたが与えたサンプルコードでは、後で何らかの理由でハッシュの具体的な実装を変更でき、残りのコードを変更する必要がないことを意味します。

ソフトウェアの問題は、レンガやモルタルを無駄にせずに後で物事を変更することが比較的容易であるため、人々はそのような先見性はしばらくの間価値がないと考えていることです。しかし、現実には、ソフトウェアのメンテナンスには非常に費用がかかることが示されています。

10

インターフェース分離の原則 (--Iの 'I'は [〜#〜] solid [〜#〜] )に従います。これらのオブジェクトを使用するコードが、不要なオブジェクトのメソッドに依存しないようにすることで、コードの結合が少なくなり、変更が容易になります。

たとえば、後でLinkedHashMapが本当に必要であることがわかった場合、他のコードに影響を与えることなく、その変更を安全に行うことができます。

ただし、オブジェクトをパラメーターとして受け取ることができるコードを人為的に制限しているため、トレードオフがあります。何らかの理由でHashMap要求する関数がどこかにあるとしましょう。 Mapを返す場合、オブジェクトをその関数に渡すことはできません。より具体的なクラスにある追加の機能が必要になる可能性と、結合を制限し、パブリックインターフェイスをできるだけ小さくしたいという要望のバランスをとる必要があります。

4
Karl Bielefeldt

変数をインターフェースに制約することで、その変数の使用がインターフェースに存在しない可能性のあるHashMap固有の機能を使用しないことが保証されるため、インスタンスは後で別の実装に関係なく変更される可能性があります。新しいインスタンスもインターフェースを実装します。

このため、オブジェクトインターフェイスを使用する場合は常に、特定の実装ではなくインターフェイスとして変数を宣言することをお勧めします。これは、インターフェイスを持つすべてのタイプのオブジェクトに当てはまります。よく目にする理由は、多くの人がこれを習慣として組み込んでいるからです。

とは言っても、インターフェースの使用をスキップすることは時々害を及ぼすものではなく、ほとんどの人はalwaysをこの規則に従っていないので、実際には害はありません。コードが変更される可能性があり、将来的にメンテナンス/拡張が必要になると感じた場合は、それを守ることをお勧めします。コードをハッキングしているときに、長寿命になるか、非常に重要であると思わない場合でも、それほど心配する必要はありません。また、この規則を破ると、通常、実装を別の実装に変更するときに少しリファクタリングが必要になる可能性があるという小さな影響があります。そのため、常に従わない場合でも、多くの害を及ぼすことはありません。 。

3
Jimmy Hoffa