Зачем использовать кортежи вместо объектов? - PullRequest
44 голосов
/ 24 марта 2009

База кода, где я работаю, имеет объект Pair, где A и B являются типами первого и второго значений в Pair. Я считаю этот объект оскорбительным, потому что он используется вместо объекта с четко именованными членами. Итак, я нахожу это:

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();

Многие другие виды использования пары в кодовой базе также плохо пахнут.

Я гуглил кортежи, и их, похоже, часто неправильно понимали или использовали сомнительными способами. Есть ли убедительный аргумент за или против их использования? Я могу оценить нежелание создавать огромные иерархии классов, но существуют ли реалистичные кодовые базы, где иерархия классов взорвалась бы, если бы кортежи не использовались?

Ответы [ 10 ]

36 голосов
/ 24 марта 2009

Прежде всего, кортеж быстрый и простой: вместо того, чтобы писать класс для каждого раза, когда вы хотите собрать две вещи вместе, есть шаблон, который сделает это за вас.

Во-вторых, они являются общими. Например, в C ++ std :: map использует std :: pair ключа и значения. Таким образом, можно использовать ЛЮБУЮ пару вместо того, чтобы создавать какой-то класс-оболочку с методами доступа для каждой перестановки двух типов.

Наконец, они полезны для возврата нескольких значений. На самом деле нет причин делать класс специально для нескольких возвращаемых значений функции, и их не следует рассматривать как один объект, если они не связаны.

Если честно, код, который вы вставили, является неправильным использованием пары.

16 голосов
/ 24 марта 2009

Кортежи постоянно используются в Python, где они интегрированы в язык и очень полезны (они допускают множественные возвращаемые значения для начинающих).

Иногда вам просто нужно соединить вещи и создать настоящий, честный перед богом, класс - это излишество. С другой стороны, использование кортежей, когда вы действительно должны использовать класс, является такой же плохой идеей, как и обратная.

10 голосов
/ 24 марта 2009

Пример кода имеет несколько различных запахов:

  • Изобретая колесо

В фреймворке уже есть кортеж; структура KeyValuePair. Это используется классом Dictionary для хранения пар, но вы можете использовать его в любом месте. (Не говоря, что он подходит в этом случае ...)

  • Изготовление квадратного колеса

Если у вас есть список пар, лучше использовать структуру KeyValuePair, чем класс с той же целью, так как это приводит к меньшему выделению памяти.

  • Скрытие намерения

Класс со свойствами четко показывает, что означают значения, в то время как класс Pair<int,int> ничего не говорит вам о том, что представляют значения (только то, что они, вероятно, как-то связаны). Чтобы сделать код достаточно понятным с таким списком, вам нужно дать ему очень описательное имя, например productIdAndQuantityPairs ...

8 голосов
/ 24 марта 2009

Что бы ни стоило, код в OP является беспорядком не потому, что он использует кортежи, а потому, что значения в кортеже слишком слабо типизированы. Сравните следующее:

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

Я бы тоже расстроился, если бы моя команда разработчиков передавала идентификаторы, а не экземпляры классов, потому что целое число может представлять что угодно .


С учетом вышесказанного, кортежи чрезвычайно полезны, когда вы правильно их используете:

  • Существуют кортежи для группировки специальных значений вместе. Они, безусловно, лучше, чем создавать чрезмерное количество классов-оболочек.
  • Полезная альтернатива параметрам 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 # слишком громоздок, чтобы использовать их максимально эффективно.

4 голосов
/ 24 марта 2009

Это повторное использование кода. Вместо того, чтобы писать еще один класс с точно такой же структурой, как у последних 5 классов, подобных Tuple, которые мы создали, вы создаете ... класс Tuple и используете его всякий раз, когда вам нужен кортеж.

Если единственное значение класса - «хранить пару значений», то я бы сказал, что использование кортежей - очевидная идея. Я бы сказал, что это был запах кода (настолько, насколько я ненавижу этот термин), если вы начали реализовывать несколько идентичных классов просто так, чтобы вы могли переименовать два члена.

2 голосов
/ 09 декабря 2009

Scala имеет типы с кортежами, от 2-х кортежей (пар) до кортежей с 20+ элементами. См. Первые шаги к Scala (шаг 9):

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

Кортежи полезны, если вам нужно связать воедино значения для некоторой относительно ad hoc цели. Например, если у вас есть функция, которая должна возвращать два довольно несвязанных объекта, а не создавать новый класс для хранения этих двух объектов, вы возвращаете пару из функции.

Я полностью согласен с другими постерами, что кортежи могут быть использованы неправильно. Если кортеж имеет какую-либо семантику, важную для вашего приложения, вы должны вместо этого использовать правильный класс.

2 голосов
/ 24 марта 2009

Это просто код качества прототипа, который, вероятно, был разбит вместе и никогда не подвергался рефакторингу. Не исправить это просто лень.

Реальное использование для кортежей - это универсальная функциональность, которая на самом деле не заботится о том, что представляют собой составные части, а просто работает на уровне кортежей.

1 голос
/ 24 марта 2009

Многие вещи уже упоминались, но я думаю, что следует также упомянуть, что есть некоторые стили программирования, которые отличаются от ООП и для этих кортежей весьма полезны.

Функциональные языки программирования, такие как, например, Haskell, вообще не имеют классов.

1 голос
/ 24 марта 2009

Очевидным примером является пара координат (или тройка). Ярлыки не имеют значения; использование X и Y (и Z) это просто соглашение. Их унификация дает понять, что с ними можно обращаться одинаково.

0 голосов
/ 24 марта 2009

Если вы выполняете преобразование Шварца для сортировки по определенному ключу (который стоит многократно вычислять) или что-то в этом роде, класс кажется немного излишним:

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 или чего-то, что здесь кажется немного излишним.

Ваш пример не был хорошим примером кортежа, хотя, я согласен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...