25

Javaでプログラミングするとき、私はほとんどいつも、習慣から外れて、次のように書きます。

public List<String> foo() {
    return new ArrayList<String>();
}

ほとんどの場合、それについて考えることさえありません。さて、問題は次のとおりです。常にインターフェイスをリターンタイプとして指定する必要がありますか?または、インターフェイスの実際の実装を使用することをお勧めしますか?使用する場合は、どのような状況で使用しますか?

インターフェイスを使用することには多くの利点があることは明らかです(それがそこにある理由です)。ほとんどの場合、ライブラリ関数がどの具体的な実装を使用するかは重要ではありません。しかし、それが問題になる場合もあるかもしれません。たとえば、私が主にリスト内のデータにランダムにアクセスすることがわかっている場合、aLinkedListは悪いでしょう。しかし、私のライブラリ関数がインターフェイスのみを返す場合、私は単にわかりません。ArrayList安全のために、リストを明示的に:にコピーする必要があるかもしれません。

List bar = foo();
List myList = bar instanceof LinkedList ? new ArrayList(bar) : bar;

しかし、それは恐ろしいことのように思われ、私の同僚はおそらく私をカフェテリアでリンチするでしょう。そして当然のことながらそうです。

皆さんはどう思いますか?あなたのガイドラインは何ですか、いつ抽象的な解決策に向かう傾向がありますか、そしていつ潜在的なパフォーマンス向上のためにあなたの実装の詳細を明らかにしますか?

4

12 に答える 12

32

適切なインターフェースを返して、実装の詳細を非表示にします。クライアントは、オブジェクトの実装方法ではなく、オブジェクトが提供するものだけを気にする必要があります。プライベートArrayListから始めて、後で何か他のもの(LinkedLisk、スキップリストなど)がより適切であると判断した場合は、インターフェイスを返すと、クライアントに影響を与えることなく実装を変更できます。あなたが具体的なタイプを返す瞬間、機会は失われます。

于 2009-05-31T17:08:17.130 に答える
12

たとえば、主にリスト内のデータにランダムにアクセスすることがわかっている場合、LinkedListは不適切です。しかし、私のライブラリ関数がインターフェイスのみを返す場合、私は単にわかりません。安全のために、リストを明示的にArrayListにコピーする必要があるかもしれません。

他の誰もが述べているように、ライブラリの結合を減らし、ライブラリの保守性を高めるために、ライブラリがどのように機能を実装しているかを気にする必要はありません。

ライブラリクライアントとして、実装がユースケースに対してうまく機能していないことを示すことができる場合は、担当者に連絡して、従うべき最適なパスについて話し合うことができます(この場合の新しい方法または実装の変更) 。

とは言うものの、あなたの例は時期尚早の最適化の悪臭を放っています。

メソッドが重要であるか、重要である可能性がある場合は、ドキュメントに実装の詳細が記載されている場合があります。

于 2009-05-31T17:23:13.570 に答える
11

大量のCS引用符でそれを正当化することはできませんが(私は独学です)、クラスを設計するとき、私は常に「最も派生性の低いものを受け入れ、最も派生性の高いものを返す」というマントラを通り過ぎてきました。年。

つまり、インターフェイスと具体的なリターンの観点から、依存関係を減らしたり、分離したりしようとしている場合は、インターフェイスを返す方が一般的に便利だと思います。ただし、具象クラスがそのインターフェース以上のものを実装している場合は、通常、メソッドの呼び出し元が、返されたオブジェクトの機能のサブセットに限定するよりも、具象クラスを元に戻す(つまり「最も派生した」)方が便利です。 -実際に必要な場合を除いてそれらを制限します。次に、インターフェイスのカバレッジを増やすこともできます。このような不必要な制限は、クラスの無思慮な封印と比較します。あなたは、決して知らない。そのマントラの前の部分(他の読者のために)について少し話すだけで、最小の派生物を受け入れることは、あなたのメソッドの呼び出し元に最大の柔軟性を与えます。

-オシーン

于 2009-05-31T17:05:12.093 に答える
9

オブジェクト指向プログラミングでは、可能な限りデータをカプセル化する必要があります。実際の実装を可能な限り非表示にし、型を可能な限り抽象化します。

この文脈では、私は意味のあるものだけを返すと答えます。戻り値が具象クラスであることはまったく意味がありますか?別名、あなたの例では、自問してみてください。fooの戻り値にLinkedList固有のメソッドを使用する人はいますか?

  • いいえの場合は、上位レベルのインターフェイスを使用してください。はるかに柔軟性があり、バックエンドを変更できます
  • はいの場合は、自問してください。コードをリファクタリングして、より高いレベルのインターフェイスを返すことはできませんか?:)

コードが抽象的であればあるほど、バックエンドを変更するときに必要な変更は少なくなります。それはそれと同じくらい簡単です。

一方、戻り値を具象クラスにキャストすることになった場合、それはおそらく具象クラスの代わりに返す必要があるという強い兆候です。ユーザー/チームメイトは、多かれ少なかれ暗黙のコントラクトについて知る必要はありません。具体的なメソッドを使用する必要がある場合は、明確にするために具体的なクラスを返すだけです。

一言で言えば:コード抽象、しかし明示的に:)

于 2009-05-31T17:21:51.013 に答える
8

申し訳ありませんが、基本的なルールは次のとおりだと思います。

  • 入力引数には、最も一般的なを使用します。
  • 出力値の場合、最も具体的な.

したがって、この場合、実装を次のように宣言します。

public ArrayList<String> foo() {
  return new ArrayList<String>();
}

理論的根拠: 入力ケースは既に知られており、誰もが説明しています: インターフェイス、ピリオドを使用してください。ただし、出力ケースは直感に反するように見える場合があります。受信しているものについてクライアントに最も多くの情報を提供する必要があるため、実装を返します。この場合、より多くの知識がより大きな力になります。

例 1: クライアントが 5 番目の要素を取得したい場合:

  • コレクションを返す: 5 番目の要素まで繰り返す必要があります。リストを返す:
  • リストを返す:list.get(4)

例 2: クライアントが 5 番目の要素を削除したい場合:

  • リストを返す: 指定された要素なしで新しいリストを作成する必要があります (list.remove()オプションです)。
  • 戻り配列リスト:arrayList.remove(4)

したがって、インターフェースを使用することは、再利用性を促進し、結合を減らし、保守性を向上させ、人々を幸せにするので、素晴らしいことです...ただし、入力として使用する場合のみです.

したがって、ルールは次のように記述できます。

  • 提供するものには柔軟に対応してください。
  • あなたが提供するものに有益であること。

では、次回は実装を返してください。

于 2014-07-11T20:01:32.513 に答える
6

一般に、APIなどの公開インターフェースの場合List、具体的な実装(など)を介してインターフェース(など)を返すArrayList方が適切です。

ArrayListまたはの使用はLinkedList、ライブラリの最も一般的なユースケースで検討する必要があるライブラリの実装の詳細です。そしてもちろん、内部的には、privateメソッドを渡すことLinkedListは、処理を容易にする機能を提供するのであれば、必ずしも悪いことではありません。

List後で他のクラスが使用されると信じる十分な理由がない限り、実装で具象クラスを使用すべきでない理由はありません。しかし、繰り返しになりますが、公開部分が適切に設計されている限り、実装の詳細を変更することはそれほど苦痛ではありません。

ライブラリ自体は、その消費者にとってブラックボックスである必要があるため、内部で何が起こっているのかを実際に心配する必要はありません。これは、ライブラリが意図された方法で使用されるように設計されている必要があることも意味します。

于 2009-05-31T17:09:12.933 に答える
5

API メソッドがインターフェイスを返すか具象クラスを返すかは、それほど重要ではありません。ここにいる誰もが言っていることにもかかわらず、コードが書かれたら、実装クラスを変更することはほとんどありません。

さらに重要なことは、メソッドのパラメーターには常に最小スコープのインターフェイスを使用することです! そうすれば、クライアントには最大限の自由が与えられ、コードでさえ知らないクラスを使用できます。

API メソッドが を返すときArrayList、私はまったく問題ありませんが、(または、すべて共通の) パラメータを要求するときは、ArrayListプログラマーVectorを探し出して彼を傷つけることを考えます。 .Arrays.asList()Collections.singletonList()Collections.EMPTY_LIST

于 2009-05-31T21:47:37.907 に答える
4

原則として、私はライブラリのプライベートな内部作業にいる場合にのみ内部実装を返しますが、それでも控えめにしか返しません。公開されていて、モジュールの外部から呼び出される可能性が高いものはすべて、インターフェイスとファクトリパターンを使用します。

このような方法でインターフェースを使用することは、再利用可能なコードを書くための非常に信頼できる方法であることが証明されています。

于 2009-05-31T17:09:13.167 に答える
4

主な質問はすでに回答されており、常にインターフェースを使用する必要があります。ただし、コメントしたいだけです

インターフェイスを使用することには多くの利点があることは明らかです (それが存在する理由です)。ほとんどの場合、ライブラリ関数がどの具象実装を使用するかは問題ではありません。しかし、それが問題になる場合もあります。たとえば、主にリスト内のデータにランダムにアクセスすることがわかっている場合、LinkedList は適切ではありません。しかし、ライブラリ関数がインターフェイスのみを返す場合は、わかりません。安全のために、リストを明示的に ArrayList にコピーする必要さえあるかもしれません。

ランダム アクセスのパフォーマンスが低いことがわかっているデータ構造 (O(n) であり、通常は大量のデータ) を返す場合は、List の代わりに、Iterable などの他のインターフェイスを指定する必要があります。シーケンシャルアクセスのみが利用可能であることを十分に認識してください。

返す適切な型を選択することは、インターフェイスと具体的な実装だけではなく、適切なインターフェイスを選択することでもあります。

于 2009-05-31T21:27:48.960 に答える
1

インターフェイスを使用して、実際の実装から抽象化します。インターフェイスは基本的に、実装で実行できることの青写真にすぎません。

インターフェイスは、実装がインターフェイスの指示どおりに実行する限り、コンシューマーが直接影響を受けることを恐れることなく実装の詳細を変更できるため、優れた設計です。

インターフェイスを操作するには、次のようにインターフェイスをインスタンス化します。

IParser parser = new Parser();

これで、IParserがインターフェースになり、Parserが実装になります。これで、上からパーサーオブジェクトを操作するときに、インターフェイス(IParser)に対して作業し、次に、実装(Parser)に対して作業します。

つまり、パーサーの内部動作を必要なだけ変更でき、IParserパーサーインターフェイスに対して機能するコードに影響を与えることはありません。

于 2009-05-31T17:05:15.357 に答える
1

一般に、具象クラスの機能が必要ない場合は、すべての場合にインターフェイスを使用してください。リストの場合、Java はRandomAccessマーカー クラスを主に追加して、アルゴリズムが get(i) が定数時間かどうかを知る必要がある一般的なケースを区別することに注意してください。

コードの使用については、上記の Michael が正しく、メソッド パラメーターで可能な限り一般的であることは、多くの場合、さらに重要です。これは、そのようなメソッドをテストする場合に特に当てはまります。

于 2009-06-02T20:44:44.517 に答える
0

インターフェイスを返すと、コードに浸透することがわかります(または見つかりました)。たとえば、メソッドAからインターフェイスを返し、インターフェイスをメソッドBに渡す必要があります。

あなたがしているのは、限られた方法ではありますが、契約によるプログラミングです。

これにより、内部で実装を変更するための膨大な範囲が提供されます(これらの新しいオブジェクトが既存のコントラクト/期待される動作を満たしている場合)。

これらすべてを考慮すると、実装を選択すること、および動作をどのように置き換えることができるか(たとえば、モックを使用するテストを含む)の点で利点があります。ご想像のとおり、私はこれに賛成で、可能な限りインターフェースを減らす(または導入する)ようにしています。

于 2009-05-31T17:12:10.137 に答える