为什么使用元组而不是对象?
我工作的代码库有一个名为Pair的对象,其中A和B是Pair中第一个和第二个值的types。 我觉得这个对象是冒犯的,因为它被用来代替一个明确指定成员的对象。 所以我觉得这个:
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();
在代码库中对的许多其他用途同样是不好闻的。
我使用了元组,他们似乎经常被误解或用于可疑的方式。 有没有一个令人信服的论点来支持或者反对这种使用? 我可以理解,不想创build庞大的类层次结构,但是如果不使用元组,那么是否存在实际的代码库,类层次结构会发生爆炸?
首先,一个元组快速而简单:不是每次你想把两样东西拼在一起写一个类,而是有一个模板为你做。
其次,它们是通用的。 例如,在C ++中,std :: map使用一个std :: key和value对。 因此可以使用ANY对,而不必为两种types的每个置换使用访问器方法创build某种包装类。
最后,它们对于返回多个值很有用。 真的没有理由为一个函数的多个返回值创build一个类,如果它们不相关,就不应该被当作一个对象。
公平地说,你粘贴的代码是一对糟糕的使用。
元组在Python中一直使用,它们被集成到语言中,非常有用(它们允许启动器有多个返回值)。
有时候,你真的只需要配对,创造一个真正的,诚实的上帝,上课是矫枉过正的。 另一方面,当你真正使用一个类的时候使用元组就是一个相反的想法。
代码示例有一些不同的气味:
- 重新发明轮子
框架中已经有了一个元组, KeyValuePair
结构。 Dictionary
类使用这个来存储对,但是你可以在任何适合的地方使用它。 (不是说它适合这种情况…)
- 制作一个方形的轮子
如果你有一个对的列表,最好使用KeyValuePair
结构,而不是具有相同目的的类,因为它会导致内存分配的减less。
- 隐藏的意图
具有属性的类清楚地显示了这些值的含义,而Pair<int,int>
类并没有告诉你任何关于值表示的东西(只是它们可能以某种方式相关)。 为了使代码合理地自我解释,像这样的列表,你必须给列表一个非常简短的名字,像productIdAndQuantityPairs
…
对于它的价值,OP中的代码乱七八糟,不是因为它使用了元组,而是因为元组中的值太弱了。 比较以下内容:
List<Pair<Integer, Integer>> products_weak = blah1(); List<Pair<Product, Integer>> products_strong = blah2();
如果我的开发团队在周围传递ID而不是类实例,我也会感到不安,因为一个整数可以代表任何东西 。
据说,元组在使用它们的时候非常有用:
- 存在元组以将特定值组合在一起。 它们肯定比创build过多的包装类更好。
- 当您需要从函数返回多个值时,可以使用out / ref参数的有用替代方法。
但是,C#中的元组使我的眼睛水。 像OCaml,Python,Haskell,F#等许多语言都有定义元组的特殊简洁的语法。 例如,在F#中, Map模块定义了一个构造函数,如下所示:
val of_list : ('key * 'a) list -> Map<'key,'a>
我可以创build一个地图的实例:
(* 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#的语法太麻烦了,以至于无法使用它们。
这是代码重用。 我们制作了最后的5个元组类,而不是编写与另一个类完全相同的结构类,而是使用一个Tuple类,并在需要元组时使用它。
如果类的唯一意义是“存储一对值”,那么我会说使用元组是一个明显的想法。 如果你开始实现多个相同的类,那么你可以重命名这两个成员,我会说这是一种代码味道(尽pipe我讨厌这个词)。
这只是原型质量的代码,可能被砸在一起,从来没有被重构。 没有修复它只是懒惰。
元组的实际用途是为了实际上不关心组件部分是什么的genericsfunction,而只是在元组级别上运行。
Scala具有元组types,从2元组(对)到20+元组。 查看Scala的第一步(第9步):
val pair = (99, "Luftballons") println(pair._1) println(pair._2)
如果您需要将某些相对特定目的的值捆绑在一起,则元组很有用。 例如,如果你有一个需要返回两个完全不相关的对象的函数,而不是创build一个新的类来保存这两个对象,你从函数返回一个Pair。
我完全同意其他海报,元组可以被滥用。 如果一个元组有任何对你的应用程序都很重要的语义,你应该使用适当的类。
一个明显的例子是坐标对(或三重)。 标签是不相关的; 使用X和Y(和Z)只是一个惯例。 使他们统一,清楚地表明他们可以用同样的方式对待。
已经提到了很多东西,但我想也应该提一下,有一些编程风格,与OOP不同,这些元组相当有用。
像Haskell这样的函数式编程语言根本就没有类。
如果你正在做一个Schwartzian变换来sorting一个特定的键(这是昂贵的重复计算),或类似的东西,类似乎有点矫枉过正:
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}
有一个class DataAndKey
或任何似乎有点多余在这里。
你的例子不是一个元组的好例子,但我同意。