Найкраща практика передачі багатьох аргументів методу?


94

Іноді нам доводиться писати методи, які отримують безліч аргументів, наприклад:

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");
 ......
 }

Це не є доброю практикою, інкапсулювання параметрів на карту - це цілком втрата ефективності. Хороша річ в тому, що чіткий підпис легко додає інші параметри з найменшими змінами. яка найкраща практика для такого роду проблем?

Відповіді:


139

На Ефективній Java , Глава 7 (Методи), Пункт 40 (Ретельно розробляйте підписи методів), Блок пише:

Існує три методи скорочення занадто довгих списків параметрів:

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

Щоб отримати докладнішу інформацію, я рекомендую вам придбати книгу, вона справді того варта.


Що таке "занадто довгі параметри"? Коли можна сказати, що метод має забагато параметрів? Є конкретна цифра чи діапазон?
Red M

2
@RedM Я завжди вважав будь-що більше 3 або 4 параметрів "надмірно довгим"
jtate

2
@jtate це особистий вибір чи ви дотримуєтесь офіційного документа?
Red M

2
@RedM особисті уподобання :)
jtate

2
У третьому виданні Effective Java це глава 8 (методи), пункт 51
GarethOwen 02

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 місяці, його буде легше зрозуміти, коли вам потрібно виправити помилку або додати функцію.

Звичайно, це загальна філософія, і оскільки ви не надали жодних деталей, я також не можу дати вам більш детальної поради. :-)


чи не буде проблема зі збиранням сміття?
rupinderjeet

1
Ні, якщо ви робите об'єкт параметра локальною областю у функції, що викликає, і якщо ви не мутуєте його. Це, швидше за все, буде зібрано і його пам'ять буде використана досить швидко за таких обставин.
dimitarvp

Імо, у вас також повинен бути XXXParameter param = new XXXParameter();доступний, а потім використовувати XXXParameter.setObjA(objA); тощо ...
satibel

10

По-перше, я спробував би рефакторингувати метод. Якщо він використовує стільки параметрів, це може бути занадто довгим способом. Розбивши його, можна було б як покращити код, так і потенційно зменшити кількість параметрів для кожного методу. Ви також можете переформатувати всю операцію до власного класу. По-друге, я б шукав інші випадки, коли я використовую той самий (або набір) того самого списку параметрів. Якщо у вас кілька екземплярів, це, ймовірно, сигналізує про те, що ці властивості належать разом. У такому випадку створіть клас для зберігання параметрів та його використання. Нарешті, я б оцінив, чи варто за кількістю параметрів варто створювати об’єкт карти для покращення читабельності коду. Я думаю, що це особистий дзвінок - у будь-якому випадку з цим рішенням виникає біль, і де компромісна точка може відрізнятися. За шістьма параметрами я б, мабуть, цього не робив. Для 10 я, мабуть, би (якби жоден з інших методів не працював першим).


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

7

Існує шаблон, який називається об'єктом Parameter .

Ідея полягає у використанні одного об’єкта замість усіх параметрів. Тепер, навіть якщо вам потрібно додати параметри пізніше, вам просто потрібно додати їх до об'єкта. Інтерфейс методу залишається незмінним.


5

Ви можете створити клас для зберігання цих даних. Потрібно бути достатньо значущим, але набагато кращим, ніж використання карти (OMG).


Я не думаю, що потрібно створювати клас для зберігання параметра методу.
Сойєр

Я б створив клас лише в тому випадку, якщо було кілька випадків передачі однакових параметрів. Це означало б, що параметри пов’язані і, мабуть, все одно належать разом. Якщо ви створюєте клас для одного методу, лікування, мабуть, гірше, ніж захворювання.
tvanfosson

Так - ви можете перемістити відповідні параметри в DTO або об'єкт значення. Чи є деякі з декількох параметрів необов’язковими, тобто основний метод перевантажений цими додатковими параметрами? У таких випадках - я вважаю це прийнятним.
JoseK

Це те, що я мав на увазі, сказавши, має бути досить значущим.
Йоганнес Рудольф

4

Code Complete * пропонує кілька речей:

  • "Обмежте кількість параметрів процедури до приблизно семи. Сім - це магічне число для розуміння людьми" (стор. 108).
  • "Розмістіть параметри у порядку введення-модифікації-виведення ... Якщо кілька підпрограм використовують подібні параметри, розміщуйте подібні параметри у послідовному порядку" (стор. 105).
  • Останнє ставлення змінних стану або помилок.
  • Як згадував tvanfosson , передайте лише ті частини структурованих змінних (об'єктів), які потрібні програмі. Тим не менш, якщо ви використовуєте більшу частину структурованої змінної у функції, тоді просто передайте всю структуру, але пам’ятайте, що це сприяє зв’язці до певної міри.

* Перше видання, я знаю, що мені слід оновити. Крім того, цілком ймовірно, що деякі з цих порад могли змінитися з часу написання другого видання, коли ООП починав ставати більш популярним.


2

Хорошою практикою буде рефакторинг. Що з цими об’єктами означає, що їх слід передавати до цього методу? Чи слід їх інкапсулювати в один об’єкт?


так вони повинні. Наприклад, велика форма пошуку має багато не пов'язаних між собою обмежень і потреб у пагінації. вам потрібно пройти currentPageNumber, searchCriteria, pageSize ...
Sawyer

2

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

Більш чистим способом було б групувати всі параметри в об'єктному компоненті, але це все одно не вирішує проблему повністю.

Те, що у вас тут, - це проблема дизайну. Маючи понад 7 параметрів методу, у вас почнуть виникати проблеми з запам’ятовуванням того, що вони представляють і який порядок у них є. Звідси ви отримаєте багато помилок, просто викликавши метод у неправильному порядку параметрів.

Вам потрібен кращий дизайн програми, а не найкраща практика надсилання безлічі параметрів.


1

Створіть клас bean і встановіть усі параметри (метод setter) і передайте цей об'єкт bean методу.


1
  • Подивіться на свій код і подивіться, чому передаються всі ці параметри. Іноді можливо рефакторинг самого методу.

  • Використання карти робить ваш метод уразливим. Що робити, якщо хтось, хто використовує ваш метод, неправильно пише ім’я параметра або публікує рядок, де ваш метод очікує UDT?

  • Визначте об’єкт передачі . Це забезпечить вам перевірку типу принаймні; можливо, навіть можливо виконати певну перевірку в точці використання, а не в межах вашого методу.



0

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


0

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

  1. Карта - Ви згадали про ефективність, але тут є більша проблема:

    • Користувачі, які телефонують, не знають, що вам відправити, не посилаючись на щось
      інше ... Чи є у вас javadocs, де вказано, які саме ключі та
      значення використовуються? Якщо ви це зробите (що чудово), то наявність багатьох параметрів теж не є проблемою.
    • Прийняти різні типи аргументів стає дуже важко. Ви можете обмежити вхідні параметри одним типом, або скористатися 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);
}

Може використовувати його: тут він може зробити трохи більше. По-перше, він може враховувати параметри для 3 викликів методів. він також може виконувати 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, якщо вони будуть прийняті без необхідності, матимуть довгострокові негативні наслідки з точки зору обслуговування через велику кількість дублікатів коду, який він генерує. Чим більше коду, тим більше його потрібно підтримувати, тим більше часу і грошей витрачається на його підтримку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.