Лучшая практика для передачи многих аргументов методу?

95

Иногда нам приходится писать методы, которые получают много аргументов, например:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

Когда я сталкиваюсь с подобной проблемой, я часто инкапсулирую аргументы в карту.

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

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

Сойер
источник

Ответы:

140

В Эффективной Java , глава 7 (Методы), пункт 40 (Тщательно продумайте сигнатуры методов), Блох пишет:

Есть три метода сокращения слишком длинных списков параметров:

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

Для получения дополнительных сведений советую купить книгу, она того стоит.

JRL
источник
Что такое «слишком длинные параметры»? Когда можно сказать, что у метода слишком много параметров? Есть конкретное число или диапазон?
Red M
2
@RedM Я всегда считал, что параметры, превышающие 3 или 4, являются "чрезмерно длинными"
jtate
2
@jtate - это личный выбор или вы следите за официальным документом?
Red M
2
@RedM личные предпочтения :)
jtate
2
В третьем издании Effective Java это глава 8 (методы), пункт 51
GarethOwen
71

Использование карты с волшебными клавишами String - плохая идея. Вы теряете любую проверку времени компиляции, и действительно неясно, какие параметры требуются. Вам нужно будет написать очень полную документацию, чтобы это исправить. Вы вспомните через несколько недель, что это за строки, не глядя на код? Что, если вы допустили опечатку? Используете неправильный тип? Вы не узнаете, пока не запустите код.

Вместо этого используйте модель. Создайте класс, который будет контейнером для всех этих параметров. Таким образом вы сохраните безопасность типов Java. Вы также можете передать этот объект другим методам, поместить его в коллекции и т. Д.

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

Том
источник
24

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

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

Используя статический импорт, вы можете создавать внутренние плавные API:

... .datesBetween(from(date1).to(date2)) ...
Ха.
источник
3
Что, если все параметры обязательны, а не обязательны?
emeraldhieu
1
Таким образом вы также можете иметь параметры по умолчанию. Кроме того, шаблон построителя связан с плавными интерфейсами. Думаю, это действительно должно быть ответом. Помимо разбивки длинного конструктора на более мелкие методы инициализации, которые не являются обязательными.
Эхтеш Чоудхури
13

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

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

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

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

dimitarvp
источник
будет ли сборка мусора проблемой?
rupinderjeet,
1
Нет, если вы сделаете объект параметра локальной областью действия в вызывающей функции и не измените его. Скорее всего, в таких обстоятельствах он будет собран и его память будет повторно использована довольно быстро.
dimitarvp 07
Imo, у вас также должен быть XXXParameter param = new XXXParameter();доступный, а затем использовать XXXParameter.setObjA(objA); и т.д ...
satibel
10

Сначала я бы попробовал реорганизовать метод. Если он использует такое количество параметров, это может оказаться слишком длинным. Его разрушение улучшило бы код и потенциально уменьшило бы количество параметров для каждого метода. Вы также можете провести рефакторинг всей операции до ее собственного класса. Во-вторых, я бы поискал другие случаи, когда я использую тот же (или расширенный набор) того же списка параметров. Если у вас несколько экземпляров, это, вероятно, сигнализирует о том, что эти свойства принадлежат друг другу. В этом случае создайте класс для хранения параметров и его использования. Наконец, я бы оценил, оправдывает ли количество параметров создание объекта карты для улучшения читаемости кода. Я думаю, что это личное дело каждого - это решение сопряжено с трудностями, и точки компромисса могут отличаться. По шести параметрам я, наверное, не стал бы этого делать. Для 10 я бы, вероятно, сделал бы (если бы ни один из других методов сначала не сработал).

tvanfosson
источник
8

Это часто проблема при построении объектов.

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

Вы также можете адаптировать его к вызову метода.

Это также значительно увеличивает удобочитаемость.

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

Вы также можете поместить логику проверки в методы Builder set .. () и build ().

Богдан
источник
Что бы вы порекомендовали, если многие из ваших полей такие final? Это главное, что мешает мне писать вспомогательные функции. Я полагаю, что могу сделать поля частными и убедиться, что я не изменяю их неправильно в коде этого класса, но я надеюсь на что-то более элегантное.
ragerdl 05
7

Есть шаблон, который называется объектом Parameter .

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

Падмараг
источник
5

Вы можете создать класс для хранения этих данных. Тем не менее, он должен быть достаточно значимым, но намного лучше, чем использование карты (OMG).

Иоганнес Рудольф
источник
Я не думаю, что необходимо создавать класс для хранения параметра метода.
Сойер,
Я бы создал класс только в том случае, если было несколько экземпляров передачи одних и тех же параметров. Это будет сигнализировать о том, что параметры связаны и, вероятно, в любом случае принадлежат друг другу. Если вы создаете класс для одного метода, лекарство, вероятно, хуже, чем болезнь.
tvanfosson,
Да - вы можете переместить связанные параметры в DTO или объект значения. Являются ли некоторые из нескольких параметров необязательными, т.е. основной метод перегружен этими дополнительными параметрами? В таких случаях - я считаю, что это приемлемо.
JoseK
Вот что я имел в виду, говоря, что должно быть достаточно значимым.
Йоханнес Рудольф,
4

Code Complete * предлагает несколько вещей:

  • «Ограничьте количество параметров процедуры примерно семью. Семь - магическое число для понимания людей» (стр. 108).
  • «Поместите параметры в порядке ввода-изменения-вывода ... Если несколько подпрограмм используют похожие параметры, разместите аналогичные параметры в последовательном порядке» (стр. 105).
  • Переменные состояния или ошибки ставьте последними.
  • Как упоминал tvanfosson , передайте только те части структурированных переменных (объектов), которые нужны подпрограмме. Тем не менее, если вы используете большую часть структурированной переменной в функции, просто передайте всю структуру, но имейте в виду, что это в некоторой степени способствует связыванию.

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

Джастин Джонсон
источник
2

Хорошей практикой будет рефакторинг. Что насчет этих объектов означает, что они должны быть переданы в этот метод? Должны ли они быть инкапсулированы в один объект?

GaryF
источник
да, они должны. Например, большая форма поиска имеет много не связанных ограничений и потребностей в разбивке на страницы. вам нужно передать currentPageNumber, searchCriteria, pageSize ...
Sawyer
2

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

Более чистый способ - сгруппировать все параметры в объектном компоненте, но это все еще не решает проблему полностью.

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

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


источник
1

Создайте класс компонента, установите все параметры (метод установки) и передайте этот объект компонента методу.

Тони
источник
1
  • Посмотрите на свой код и поймите, почему передаются все эти параметры. Иногда можно провести рефакторинг самого метода.

  • Использование карты делает ваш метод уязвимым. Что, если кто-то, использующий ваш метод, неправильно напишет имя параметра или отправит строку, в которой ваш метод ожидает UDT?

  • Определите объект передачи . Это как минимум обеспечит вам проверку типов; возможно, вы даже сможете выполнить некоторую проверку в точке использования, а не внутри вашего метода.

Каждый
источник
0

Это часто указывает на то, что ваш класс несет более одной ответственности (т. Е. Ваш класс делает СЛИШКОМ много).

См. Принцип единой ответственности

для получения дополнительной информации.

вспомогательный метод
источник
0

Если вы передаете слишком много параметров, попробуйте выполнить рефакторинг метода. Может быть, он делает много вещей, которых не должен делать. Если это не так, попробуйте заменить параметры одним классом. Таким образом вы можете инкапсулировать все в одном экземпляре класса и передавать экземпляр, а не параметры.

азамшарп
источник
0

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

  1. Карта - Вы упомянули об эффективности, но вот более серьезная проблема:

    • Абоненты не знают, что вам послать, не обращаясь ни к чему
      другому ... У вас есть документация javadoc, в которой точно указано, какие ключи и
      значения используются? Если да (и это здорово), то наличие большого количества параметров тоже не проблема.
    • Становится очень сложно принимать разные типы аргументов. Вы можете либо ограничить входные параметры одним типом, либо использовать Map <String, Object> и привести все значения. Оба варианта в большинстве случаев ужасны.
  2. Объекты-оболочки - это просто устраняет проблему, поскольку вам нужно заполнить объект-оболочку в первую очередь - вместо того, чтобы напрямую к вашему методу, это будет к конструктору объекта параметра. Определение целесообразности перемещения проблемы зависит от повторного использования указанного объекта. Например:

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

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

Может использовать: Здесь он может немного больше. Во-первых, он может учитывать параметры для трех вызовов методов. он также может выполнять 2 другие строки сам по себе ... так что он становится в некотором смысле переменной состояния ...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. Паттерн Строитель - это на мой взгляд антипаттерн. Наиболее желательный механизм обработки ошибок - обнаруживать раньше, а не позже; но с шаблоном компоновщика вызовы с отсутствующими (программист не подумал включить) обязательными параметрами перемещаются из времени компиляции во время выполнения. Конечно, если программист намеренно поместил в слот null или что-то подобное, это будет время выполнения, но все же обнаружение некоторых ошибок на более раннем этапе является гораздо большим преимуществом для программистов, которые отказываются смотреть на имена параметров вызываемого ими метода. Я считаю это целесообразным только при работе с большим количеством необязательных параметров, и даже в этом случае выгода в лучшем случае незначительна. Я категорически против "выкройки" строителя.

Другая вещь, которую люди забывают учитывать, - это роль IDE во всем этом. Когда у методов есть параметры, IDE генерируют для вас большую часть кода, и у вас есть красные линии, напоминающие вам, что вам нужно предоставить / установить. При использовании варианта 3 ... вы полностью теряете это. Теперь программист должен сделать это правильно, и во время кодирования и компиляции нет никаких подсказок ... программист должен проверить это, чтобы узнать.

Кроме того, варианты 2 и 3, если они будут приняты широко и без необходимости, будут иметь долгосрочные негативные последствия с точки зрения обслуживания из-за большого количества генерируемого дублированного кода. Чем больше кода, тем больше нужно поддерживать, тем больше времени и денег тратится на его поддержку.

Джон
источник