45

私が作業しているコードベースには、A と B がペアの最初と 2 番目の値の型であるペアというオブジェクトがあります。このオブジェクトは、明確に名前が付けられたメンバーを持つオブジェクトの代わりに使用されるため、攻撃的であることがわかりました。だから私はこれを見つけます:

List<Pair<Integer, Integer>> productIds = blah();
// snip many lines and method calls

void doSomething(Pair<Integer, Integer> id) {
  Integer productId = id.first();
  Integer quantity = id.second();
}

それ以外の

class ProductsOrdered {
  int productId;
  int quantityOrdered;
  // accessor methods, etc
}

List<ProductsOrderded> productsOrdered = blah();

コードベースでの Pair の他の多くの使用法も、同様に悪臭を放っています。

私はタプルをグーグルで検索しましたが、それらはしばしば誤解されたり、疑わしい方法で使用されたりしているようです。それらの使用に賛成または反対する説得力のある議論はありますか? 巨大なクラス階層を作成したくないのは理解できますが、タプルを使用しないとクラス階層が爆発する現実的なコードベースはありますか?

4

10 に答える 10

36

まず第一に、タプルは素早く簡単です: 2 つのものをまとめたいと思うたびにクラスを書く代わりに、それを行うテンプレートがあります。

第二に、それらは一般的です。たとえば、C++ では、std::map はキーと値の std::pair を使用します。したがって、2 つの型の順列ごとにアクセサ メソッドを使用してある種のラッパー クラスを作成する代わりに、任意のペアを使用できます。

最後に、複数の値を返す場合に便利です。関数の複数の戻り値専用のクラスを作成する理由は実際にはありません。また、それらが関連していない場合は、1 つのオブジェクトとして扱われるべきではありません。

公平を期すために、貼り付けたコードはペアの不適切な使用です。

于 2009-03-23T21:11:51.050 に答える
16

タプルは Python で常に使用されており、言語に統合されており、非常に便利です (初心者向けに複数の戻り値を許可します)。

場合によっては、物事をペアにする必要があり、本物の、神に正直なクラスを作成するのはやり過ぎです。一方で、実際にクラスを使用する必要があるときにタプルを使用することは、その逆と同じくらい悪い考えです。

于 2009-03-23T21:13:59.000 に答える
10

コード例には、いくつかの異なる匂いがあります。

  • 車輪の再発明

フレームワークで利用可能なタプルがすでにあります。KeyValuePair構造。これはDictionaryペアを格納するためにクラスによって使用されますが、それが収まる場所ならどこでも使用できます。(この場合に当てはまるとは言えません...)

  • 四角い車輪を作る

ペアのリストがある場合はKeyValuePair、同じ目的のクラスよりも構造体を使用する方がメモリ割り当てが少なくて済むため、より良い結果が得られます。

  • 意図を隠す

プロパティを持つクラスは、値が何を意味するかを明確に示しますが、Pair<int,int>クラスは値が何を表しているかについて何も教えてくれません (おそらく何らかの形で関連しているということだけです)。そのようなリストでコードを合理的に自明にするためには、リストに非常にわかりやすい名前を付ける必要がありますproductIdAndQuantityPairs...

于 2009-03-23T21:48:10.307 に答える
8

タプルを使用しているからではなく、タプルの値の型付けが弱すぎるため、OP のコードが混乱しています。以下を比較してください。

List<Pair<Integer, Integer>> products_weak = blah1();
List<Pair<Product, Integer>> products_strong = blah2();

開発チームがクラス インスタンスではなく ID を渡していたら、私も動揺します。整数は何でも表すことができるからです。


そうは言っても、タプルは正しく使用すると非常に便利です。

  • タプルは、アドホックな値をグループ化するために存在します。過剰な数のラッパー クラスを作成するよりも確実に優れています。
  • 関数から複数の値を返す必要がある場合に、out/ref パラメーターの代替として役立ちます。

しかし、C# のタプルには涙が出ます。OCaml、Python、Haskell、F# などの多くの言語には、タプルを定義するための特別で簡潔な構文があります。たとえば、F# では、Map モジュールはコンストラクターを次のように定義します。

val of_list : ('key * 'a) list -> Map<'key,'a>

以下を使用して、マップのインスタンスを作成できます。

(* val values : (int * string) list *)
let values = 
    [1, "US";
     2, "Canada";
     3, "UK";
     4, "Australia";
     5, "Slovenia"]

(* val dict : Map<int, string> *)
let dict = Map.of_list values

C# の同等のコードはばかげています。

var values = new Tuple<int, string>[] {
     new Tuple<int, string>(1, "US"),
     new Tuple<int, string>(2, "Canada"),
     new Tuple<int, string>(3, "UK"),
     new Tuple<int, string>(4, "Australia"),
     new Tuple<int, string>(5, "Slovenia")
     }

 var dict = new Dictionary<int, string>(values);

原則として、タプルに問題があるとは思いませんが、C# の構文は面倒すぎて、タプルを最大限に活用できません。

于 2009-03-23T21:33:10.030 に答える
5

コードの再利用です。最後に作成した 5 つのタプルに似たクラスとまったく同じ構造を持つさらに別のクラスを作成するのではなく、タプル クラスを作成し、タプルが必要なときにいつでもそれを使用します。

クラスの唯一の意味が「値のペアを格納すること」である場合、タプルを使用することは明らかなアイデアだと思います。2 つのメンバーの名前を変更できるようにするためだけに複数の同一のクラスを実装し始めた場合、それは (私がこの用語を嫌いますが) コードの臭いだと思います。

于 2009-03-23T21:49:54.890 に答える
2

Scala には、2 タプル (ペア) から 20 以上の要素を持つタプルまで、タプル値型があります。Scala への最初のステップ(ステップ 9)を参照してください。

val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)

タプルは、比較的アドホックな目的で値をまとめる必要がある場合に便利です。たとえば、2 つのオブジェクトを保持する新しいクラスを作成するのではなく、2 つのまったく無関係なオブジェクトを返す必要がある関数がある場合、関数から Pair を返します。

タプルが悪用される可能性があるという他のポスターに完全に同意します。タプルにアプリケーションにとって重要なセマンティクスがある場合は、代わりに適切なクラスを使用する必要があります。

于 2009-12-08T21:23:58.370 に答える
2

これは単なるプロトタイプ品質のコードであり、おそらく一緒に壊され、リファクタリングされたことはありません. それを修正しないのはただの怠惰です。

タプルの実際の用途は、コンポーネント パーツが何であるかを実際には気にせず、タプル レベルで動作するだけの汎用機能です。

于 2009-03-23T21:11:51.923 に答える
1

すでに多くのことが言及されていますが、OOP とは異なるいくつかのプログラミング スタイルがあり、それらのタプルは非常に便利であることにも言及する必要があると思います。

たとえば、Haskell のような関数型プログラミング言語には、クラスがまったくありません。

于 2009-03-23T21:35:49.307 に答える
1

明らかな例は、座標ペア (またはトリプル) です。ラベルは関係ありません。X と Y (および Z) を使用するのは単なる規則です。それらを均一にすることで、同じように扱えることが明らかになります。

于 2009-03-23T21:26:36.850 に答える
0

特定のキー (繰り返し計算するとコストがかかる) でソートするシュワルツ変換を実行している場合、またはそのようなものを実行している場合、クラスは少しやり過ぎのようです:

val transformed = data map {x => (x.expensiveOperation, x)}
val sortedTransformed = transformed sort {(x, y) => x._1 < y._1}
val sorted = sortedTransformed map {case (_, x) => x}

ここでaclass DataAndKeyまたは何でも持つことは、少し不必要に思えます。

あなたの例はタプルの良い例ではありませんでしたが、同意します。

于 2009-03-23T22:39:25.223 に答える