Как вы возвращаете два значения из одного метода?

Когда вы в ситуации, когда вам нужно вернуть две вещи одним методом, какой лучший подход?

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

Параметры, которые у меня возникли:

  • Используйте глобальные переменные для хранения возвратов. Я лично стараюсь избегать глобальных привязок, где могу.
  • Передайте две пустые переменные в качестве параметров, затем назначьте переменные внутри метода, который теперь является void. Мне не нравится идея методов, которые имеют побочные эффекты.
  • Возвращает коллекцию, содержащую две переменные. Это может привести к запутыванию кода.
  • Создайте класс контейнера для сохранения двойного возврата.. Это более самодокументирующийся, чем коллекция, содержащая другие коллекции, но похоже, что это может смущать создание класса только с целью возвращение.

Ответ 1

Это не совсем язык-агностик: в Lisp вы можете фактически вернуть любое количество значений из функции, включая (но не ограничиваясь): none, one, two,...

(defun returns-two-values ()
  (values 1 2))

То же самое справедливо для Схемы и Дилана. В Python я фактически использовал бы кортеж, содержащий 2 значения, например

def returns_two_values():
   return (1, 2)

Как указывали другие, вы можете вернуть несколько значений с помощью параметров out в С#. В С++ вы использовали бы ссылки.

void 
returns_two_values(int& v1, int& v2)
{
    v1 = 1; v2 = 2;
}

В C ваш метод будет принимать указатели на местоположения, где ваша функция должна сохранять значения результата.

void 
returns_two_values(int* v1, int* v2)
{
    *v1 = 1; *v2 = 2;
}

Для Java я обычно использую либо выделенный класс, либо довольно общий помощник (в настоящее время в моей частной библиотеке "commons" есть две: Pair<F,S> и Triple<F,S,T>, как не более, чем простые неизменяемые контейнеры для 2 или 3 значения)

Ответ 2

Я бы создал объекты передачи данных. Если это группа информации (имя и фамилия), я бы сделал класс Name и вернул его. # 4 - путь. Похоже, что больше работы впереди (что это такое), но позже становится яснее.

Если это список записей (строк в базе данных), я бы возвратил некоторую коллекцию.

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

Ответ 3

Не мои собственные мысли (дядя Боб):

Если есть связь между этими двумя переменными - я слышал, как он сказал, вы пропускаете класс, в котором эти два поля. (Он сказал то же самое о функциях с длинными списками параметров.)

С другой стороны, если нет сцепления, то функция делает больше чем одну вещь.

Ответ 4

В мире C/С++ было бы довольно распространено передавать две переменные по ссылке (пример, ваш № 2).

Ответ 5

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

Ответ 6

Я думаю, все зависит от сценария.

Думая о менталитете С#:

1: Я бы избегал глобальных решений как решение этой проблемы, поскольку это воспринимается как плохая практика.

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

3: Коллекция - отличный вариант, если возвращаемые значения одного типа и могут рассматриваться как коллекция. Однако, если конкретный пример требует 2 элемента, а каждый элемент - это "собственная" вещь → возможно, один представляет начало чего-то, а другой представляет собой конец, а возвращенные элементы не используются взаимозаменяемо, тогда это может не быть быть лучшим вариантом.

2: Мне нравится этот вариант лучше, если 4 и 3 не имеют смысла для вашего сценария. Как указано в 3, если вы хотите получить два объекта, которые представляют собой начальные и конечные элементы чего-то. Затем я буду использовать параметры по ссылке (или вне параметров, опять же, в зависимости от того, как все это используется). Таким образом, ваши параметры могут явно определять свою цель: MethodCall (ref object StartObject, ref object EndObject)

Ответ 7

Лично я пытаюсь использовать языки, которые позволяют функциям возвращать нечто большее, чем простое целочисленное значение.

Во-первых, вы должны различать то, что вы хотите: возврат произвольной длины или возврат фиксированной длины.

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

Но иногда вам просто нужно вернуть два значения. Как вернуть два значения - когда вы уверены, что всегда два значения - отличаются от возврата одного значения? Я не говорю, что это не так! И современные языки, включая perl, ruby, С++, python, ocaml и т.д., Позволяют возвращать кортежи, встроенные или в качестве стороннего синтаксического сахара (да, я говорю о boost:: tuple). Это выглядит так:

tuple<int, int, double> add_multiply_divide(int a, int b) {
  return make_tuple(a+b, a*b, double(a)/double(b));
}

Задание "выходного параметра", на мой взгляд, чрезмерно используется из-за ограничений более старых языков и парадигм, изученных в те дни. Но все же есть много случаев, когда они пригодны для использования (если ваш метод должен изменить объект, переданный как параметр, этот объект не является классом, который содержит метод).

Вывод состоит в том, что нет общего ответа - каждая ситуация имеет свое собственное решение. Но есть одна общая вещь: это не нарушение какой-либо парадигмы, что функция возвращает несколько элементов. То, что языковое ограничение позже каким-то образом передается человеческому разуму.

Ответ 8

Python (например, Lisp) также позволяет вам возвращать любое количество значения из функции, включая (но не ограничиваясь ими) none, one, two

def quadcube (x):
     return x**2, x**3

a, b = quadcube(3)

Ответ 9

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

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

Ответ 10

  • Некоторые языки делают занятие # 3 родным и легким. Пример: Perl. "return ($ a, $b);". Тоже Lisp.

  • Если это не так, проверьте, имеет ли ваш язык коллекцию, подходящую для задачи, ala pair/tuple в С++

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

Ответ 11

Если ваша функция имеет возвращаемые значения, она предположительно возвращает ее/их для назначения либо переменной, либо подразумеваемой переменной (например, для выполнения операций). Все, что вы можете с пользой выразить в виде переменной (или проверяемая ценность) должна быть честной игрой и должна диктовать то, что вы возвращаете.

В вашем примере упоминается строка или набор строк из SQL-запроса. Затем вы разумно должны быть готовы обращаться к ним как к объектам или массивам, что предполагает соответствующий ответ на ваш вопрос.

Ответ 12

Когда вы в ситуации, когда вы нужно вернуть две вещи в одном метод, каков наилучший подход?

Это зависит от того, ПОЧЕМУ вы возвращаете две вещи. В принципе, поскольку все здесь, кажется, согласны, №2 и №4 - два лучших ответа...

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

Если две части данных из базы данных связаны между собой, например, имя клиента и фамилия клиента, я бы по-прежнему рассматривал это как "одно". С другой стороны, предположим, что вы придумали странный оператор SELECT, который возвращает общую стоимость продаж вашей компании за определенную дату, а также читает имя клиента, разместившего первую продажу на сегодняшний день. Здесь вы делаете две несвязанные вещи! Если действительно верно, что выполнение этого странного оператора SELECT намного лучше, чем выполнение двух операторов SELECT для двух разных частей данных, и обе части данных действительно необходимы на регулярной основе (так что все приложение будет медленнее, если вы это не так), то использование этого странного SELECT может быть хорошей идеей - но лучше быть готовым продемонстрировать, почему ваш способ действительно влияет на воспринимаемое время отклика.

Параметры, которые у меня возникли:

1 Используйте глобальные переменные для хранения возвратов. Я лично стараюсь избегать где я могу.

Есть ситуации, когда создание глобального - это правильная вещь. Но "возвращение двух вещей из функции" не является одной из таких ситуаций. Выполнение этого для этой цели - это просто плохая идея.

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

Да, это обычно лучшая идея. Именно поэтому "по ссылке" (или "вывод", в зависимости от того, какой язык вы используете) существуют.

Мне не нравится идея методов, которые имеют побочные эффекты.

Хорошая теория, но вы можете зайти слишком далеко. Каким будет смысл вызова SaveCustomer(), если у этого метода не было побочного эффекта сохранения данных клиента? Под ссылочными параметрами понимаются параметры, содержащие возвращаемые данные.

3 Возвращает коллекцию, содержащую две переменные. Это может привести к запутыванию кода.

True. Например, было бы нецелесообразно возвращать массив, где элемент 0 был первым именем, а элемент 1 - фамилией. Это была бы плохая идея.

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

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

Ответ 13

Используйте std::vector, QList или некоторый контейнер управляемой библиотеки, чтобы удерживать столько X, которые вы хотите вернуть:

QList<X> getMultipleItems()
{
  QList<X> returnValue;
  for (int i = 0; i < countOfItems; ++i)
  {
    returnValue.push_back(<your data here>);
  }
  return returnValue;
}

Ответ 14

Используйте параметры var/out или передавайте переменные по ссылке, а не по значению. В Delphi:

function ReturnTwoValues(out Param1: Integer):Integer;
begin
  Param1 := 10;
  Result := 20;
end;

Если вы используете var вместо out, вы можете предварительно инициализировать параметр.

В базах данных у вас может быть параметр out для каждого столбца, и результат функции будет логическим, указывающим, правильно ли получена запись. (Хотя я бы использовал один класс записи для хранения значений столбца.)

Ответ 15

Насколько мне больно это сделать, я считаю, что наиболее читаемый способ возврата нескольких значений в PHP (с которыми я работаю, в основном) использует (многомерный) массив, например:

function doStuff($someThing)
{
    // do stuff
    $status  = 1;
    $message = 'it worked, good job';

    return array('status' => $status, 'message' => $message);
}

Не красиво, но это работает, и не сложно понять, что происходит.

Ответ 16

Обычно я использую кортежи. Я в основном работаю на С# и очень прост в разработке родовых кортежей. Я предполагаю, что это будет очень похоже на большинство языков, которые имеют дженерики. В стороне 1, это ужасная идея, и 3 работает только тогда, когда вы получаете два возвращения, которые являются одним и тем же типом, если вы не работаете на языке, где все происходит от одного и того же базового типа (т.е. Объекта). 2 и 4 также являются хорошим выбором. 2 не вводит никаких побочных эффектов априори, просто громоздкий.

Ответ 17

В описанной ситуации, вытаскивая два поля из одной таблицы, соответствующий ответ равен # 4, учитывая, что два свойства (поля) одного и того же объекта (таблицы) будут демонстрировать сильное сцепление.

Ваша забота о том, что "это может смущать создание класса только с целью возвращения", вероятно, не так реалистично. Если ваше приложение нетривиально, вам, вероятно, понадобится повторно использовать этот класс/объект в любом месте.

Ответ 18

Мой выбор №4. Определите ссылочный параметр в своей функции. Этот указатель ссылается на объект Value.

В PHP:

class TwoValuesVO {
  public $expectedOne;
  public $expectedTwo;
}

/* parameter $_vo references to a TwoValuesVO instance */
function twoValues( & $_vo ) {
  $vo->expectedOne = 1;
  $vo->expectedTwo = 2;
}

В Java:

class TwoValuesVO {
  public int expectedOne;
  public int expectedTwo;
}

class TwoValuesTest {
  void twoValues( TwoValuesVO vo ) {
    vo.expectedOne = 1;
    vo.expectedTwo = 2;
  }
}

Ответ 19

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

Например, у вас может быть модуль инвентаризации, который запрашивает количество виджетов в инвентаре. Возвращаемое значение, которое вы хотите указать, - это фактическое количество виджетов. Однако вы также можете записать, как часто кто-то запрашивает инвентарь и возвращает количество запросов. В этом случае может возникнуть соблазн вернуть оба значения вместе. Однако помните, что у вас есть класс vars availabe для хранения данных, поэтому вы можете хранить внутренний счетчик запросов и не возвращать его каждый раз, а затем использовать второй вызов метода для получения связанного значения. Смешайте только эти два значения, если они действительно связаны. Если это не так, используйте отдельные методы для их получения отдельно.

Ответ 20

Haskell также позволяет использовать несколько возвращаемых значений, используя встроенные кортежи:

sumAndDifference        :: Int -> Int -> (Int, Int)
sumAndDifference x y    = (x + y, x - y)

> let (s, d) = sumAndDifference 3 5 in s * d
-16

Будучи чистым языком, варианты 1 и 2 не разрешены.

Даже используя государственную монаду, возвращаемое значение содержит (по крайней мере концептуально) сумку со всем соответствующим состоянием, включая любые изменения только что сделанной функции. Это просто причудливое соглашение о передаче этого состояния через последовательность операций.

Ответ 21

Я обычно выбираю подход № 4, поскольку я предпочитаю ясность знать, что производит или вычисляет функция, это возвращаемое значение (а не параметры byref). Кроме того, он придает довольно "функциональный" стиль в потоке программы.

Недостатком варианта № 4 с общими классами классов является то, что он не намного лучше, чем возврат коллекции (единственным преимуществом является безопасность типа).

public IList CalculateStuffCollection(int arg1, int arg2)
public Tuple<int, int> CalculateStuffType(int arg1, int arg2)

var resultCollection = CalculateStuffCollection(1,2);
var resultTuple = CalculateStuffTuple(1,2);

resultCollection[0]    // Was it index 0 or 1 I wanted? 
resultTuple.A          // Was it A or B I wanted?

Мне нужен язык, который позволил бы мне вернуть неизменяемый кортеж именованных переменных (подобно словарю, но неизменяемому, типичному и статически проверенному). Но, к сожалению, такой вариант недоступен мне в мире VB.NET, он может быть где-то еще.

Мне не нравится вариант №2, потому что он нарушает этот "функциональный" стиль и заставляет вас вернуться в процедурный мир (когда часто я не хочу этого делать, просто называя простой метод, например TryParse).

Ответ 22

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

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