Javaの「インターフェイスへのプログラム」は常に意味がありますか?


9

インターフェイスから実装するクラスがどのようにインスタンス化されるかに関するこの質問での議論を見てきました。私の場合、私はのインスタンスを使用するJavaで非常に小さなプログラムを書いており、TreeMapそこでの皆の意見によると、次のようにインスタンス化する必要があります。

Map<X> map = new TreeMap<X>();

私のプログラムでmap.pollFirstEntry()は、Mapインターフェイスで宣言されていない関数(およびMapインターフェイスにも存在する他のカップル)を呼び出しています。私はTreeMap<X>次のようにこのメソッドを呼び出すすべての場所にキャストすることでこれを行うことができました:

someEntry = ((TreeMap<X>) map).pollFirstEntry();

上記の初期化ガイドラインの利点を大規模プログラムで理解していますが、このオブジェクトが他のメソッドに渡されない非常に小規模なプログラムの場合は、不要だと思います。それでも、私はこのサンプルコードを求人アプリケーションの一部として書いており、コードの見栄えを悪くしたり、散らかしたりしたくありません。最もエレガントなソリューションは何でしょうか?

編集:私は、特定の関数を適用するよりも、広く適切なコーディング方法に関心があることを指摘しておきますTreeMap。一部の回答は既に指摘されている(そして、最初に回答したものとしてマークした)ため、機能を失うことなく、可能な限り高い抽象化レベルを使用する必要があります。


1
機能が必要な場合は、ツリーマップを使用してください。これはおそらく特定の理由による設計上の選択だったので、実装の一部でもあるはずです。

@JordanReiter私は同じ内容の新しい質問を追加する必要がありますか、それとも内部クロス投稿メカニズムがありますか?
jimijazz


2
場所の上にすべてをキャストするために持つことが有利なしのどのようなプログラムのサイズではありません「大きなプログラムについて上記したように、私は初期化ガイドラインの利点を理解する」
ベン・アーロンソン

回答:


23

「インターフェースへのプログラミング」は、「可能な限り最も抽象化されたバージョンを使用する」ことを意味しません。その場合、誰もが使用するだけObjectです。

つまり、機能を失うことなく、可能な限り低い抽象化に対してプログラムを定義する必要があります。が必要な場合は、TreeMapを使用して契約を定義する必要がありますTreeMap


2
TreeMapはインターフェースではなく、その実装クラスです。Map、SortedMap、およびNavigableMapインターフェースを実装します。説明されているメソッドは、NavigableMapインターフェースの一部です。TreeMapを使用すると、実装がConcurrentSkipListMap(たとえば)に切り替わるのを防ぐことができます。

3
@MichaelT:この特定のシナリオで彼が必要とする正確な抽象化を検討しなかったのでTreeMap、例として使用しました。「インターフェースへのプログラム」は、文字通りインターフェースまたは抽象クラスとして解釈されるべきではありません。実装もインターフェースと見なすことができます。
Jeroen Vannevel

1
実装クラスのパブリックインターフェース/メソッドは技術的には「インターフェース」ですが、LSPの背後にある概念を壊し、別のサブクラスの置換を防ぎます。これがpublic interface、「実装のパブリックメソッド」ではなく、プログラミングしたい理由です。。

@JeroenVannevel インターフェースへのプログラミングは、インターフェースが実際にクラスによって表されているときに実行できることに同意します。しかし、私が使用して恩恵を受けるものを見ていないTreeMap以上のだろうSortedMapNavigableMap
toniedzwiedz

16

それでもインターフェースを使用したい場合は、使用できます

NavigableMap <X, Y> map = new TreeMap<X, Y>();

常にインターフェイスを使用する必要はありませんが、多くの場合、実装を置き換えることができるより一般的なビューを取得したい場合があります(おそらくテスト用)。オブジェクトへのすべての参照がインターフェイスタイプ。


3

実装ではなくインターフェースへのコーディングの背後にあるポイントは、そうでなければプログラムを制約する実装の詳細の漏洩を回避することです。

コードの元のバージョンがを使用し、HashMapそれを公開した状況を考えてみましょう。

private HashMap foo = new HashMap();
public HashMap getFoo() { return foo; }  // This is bad, don't do this.

これは、への変更getFoo()はAPIの重大な変更であり、それを使用する人々を不満にさせることを意味します。あなたが保証しているのがそれfooがマップだけである場合は、代わりにそれを返す必要があります。

private Map foo = new HashMap();
public Map getFoo() { return foo; }

これにより、コード内での動作方法を柔軟に変更できます。foo実際には、特定の順序で物事を返すマップである必要があることに気づきました。

private NavigableMap foo = new TreeMap();
public Map getFoo() { return foo; }
private void doBar() { ... foo.lastEntry(); ... }

そして、それは残りのコードのために何も壊しません。

後で何も壊すことなくクラスが与える契約を強化することができます。

private NavigableMap foo = new TreeMap();
public NavigableMap getFoo() { return foo; }
private void doBar() { ... foo.lastEntry(); ... }

これは、リスコフ置換原理を掘り下げます

代替可能性は、オブジェクト指向プログラミングの原則です。コンピュータプログラムでは、SがTのサブタイプである場合、タイプTのオブジェクトはタイプSのオブジェクトで置き換えられる可能性がある(つまり、タイプSのオブジェクトはタイプTのオブジェクトを置き換えることができる)そのプログラムのプロパティ(正確性、実行されたタスクなど)。

NavigableMapはMapのサブタイプであるため、この変更はプログラムを変更せずに実行できます。

実装タイプを公開すると、変更が必要な場合にプログラムの内部動作を変更することが難しくなります。これは苦痛なプロセスであり、多くの場合、後でコーダーにもっと苦痛を与えるだけの醜い回避策を作成します(私は、何らかの理由でLinkedHashMapとTreeMapの間でデータをシャッフルし続けた以前のコーダーを見ています-いつでも私を信頼してください私の心配するsvn非難であなたの名前を見てください)。

あなたはでしょう、まだ実装タイプを漏洩しないようにしたいです。たとえば、いくつかのパフォーマンス特性のために代わりにConcurrentSkipListMapを実装したい場合や、インポートステートメントなどではjava.util.concurrent.ConcurrentSkipListMapなく、単に好きな場合がありますjava.util.TreeMap


1

実際に何かが必要な最も一般的なクラス(またはインターフェース)を使用する必要があるという他の回答に同意します。この場合は、TreeMap(または誰かがNavigableMapを提案したようなもの)です。しかし、これはどこにでもキャストするよりも確かに優れていることを付け加えておきます。それはいずれにしても、はるかに大きな臭いです。何らかの理由で/programming/4167304/why-should-casting-be-avoidedを参照してください


1

それは、オブジェクトの使用方法意図伝えることです。たとえば、メソッドMap予測可能な反復順序のオブジェクトを期待する場合:

private Map<String, String> processOrderedMap(LinkedHashMap<String, String> input) {
    // ...
}

次に、上記のメソッドの呼び出し元に、予測可能な反復順序Mapオブジェクトを返すことを絶対に伝える必要がある場合は、何らかの理由でそのような期待があるためです。

private LinkedHashMap<String,String> processOrderedMap(LinkedHashMap<String,String> input) {
    // ...
}

もちろん、呼び出し元はreturnオブジェクトをMapそのままのように扱うこともできますが、それはメソッドの範囲を超えています。

private Map<String, String> output = processOrderedMap(input);

一歩下がって

インターフェイスにコーディングすることの一般的なアドバイスは、(通常)適用可能です。これは、通常、オブジェクトが実行できることを保証するインターフェイス、つまりコントラクトであるためです。多くの初心者で始まるHashMap<K, V> map = new HashMap<>()と、彼らはそのように宣言することをお勧めしますMapので、HashMapないより何よりも何かを提供Map行うことになっています。これにより、彼らは(うまくいけば)メソッドがのMap代わりにを取り込む必要がある理由を(うまくいけば)理解できるようにHashMapなり、これによってOOPの継承機能を実現できるようになります。

Wikipediaのエントリから、このトピックに関連するみんなのお気に入りの原則の 1行だけを引用するには:

階層内の型のセマンティック相互運用性を保証することを目的としているため、単なる構文関係というよりはセマンティック関係です...

言い換えると、Map宣言を使用することは、「構文的に」意味があるためではなく、オブジェクトの呼び出しは、それがのタイプであることだけを気にする必要がありMapます。

よりクリーンなコード

これにより、特に単体テストに関しては、よりクリーンなコードを書くことができることがわかりました。HashMap単一の読み取り専用テストエントリを使用してを作成すると、(ダブルブレース初期化の使用を除いて)1行以上かかりますCollections.singletonMap()

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.