Статьи

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

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

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

Перегрузка метода может быть применена по ряду причин. Одной из целей перегрузки методов может быть поддержка одной и той же функциональности для разных типов (особенно если обобщенные элементы не могут использоваться для того, чтобы метод мог поддерживать разные типы, или если методы были написаны до того, как обобщенные значения стали доступны). Примеры перегрузки методов с учетом этого намерения включают String.valueOf (boolean) , String.valueOf (char) , String.valueOf (double) , String.valueOf (long) , String.valueOf (Object) и еще несколько версий String.valueOf перегружен на несколько дополнительных типов.

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

Этот подход, состоящий из множества перегруженных конструкторов версий, каждый из которых принимает различное количество параметров, известен как телескопические конструкторы, и некоторые из них пометили его как анти-шаблон . Фактически, недостатки этого подхода к телескопическим конструкторам являются одним из факторов, на которых Джош Блох сфокусировал внимание на шаблоне Builder в пункте № 2 второго издания Effective Java . Между прочим, класс Date также предоставляет некоторые перегруженные конструкторы, предназначенные также для достижения ранее упомянутой цели, позволяя, например, Date быть составленным из String .

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

Пример параметра со слишком многими методами и перегруженными версиями

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
    * Generates and provides an instance of the Person class. This method
    * expects all characteristics of the populated Person instance and so any
    * optional or not applicable characteristics must be passed in as null.
    *
    * @param lastName
    * @param firstName
    * @param middleName
    * @param salutation
    * @param suffix
    * @param streetAddress
    * @param city
    * @param state
    * @param isFemale
    * @param isEmployed
    * @param isHomeOwner
    * @return A Person object.
    */
   public Person createPerson(
      final String lastName,
      final String firstName,
      final String middleName,
      final String salutation,
      final String suffix,
      final String streetAddress,
      final String city,
      final String state,
      final boolean isFemale,
      final boolean isEmployed,
      final boolean isHomeOwner)
   {
      // implementation goes here...
   }
 
   /**
    * Generate and provide an instance of the Person class that has only a first
    * and last name and address information. This method does not make any
    * assumptions about other characteristics of the instantiated Person, but
    * simply leaves those attributes undefined.
    *
    * @param lastName
    * @param firstName
    * @param streetAddress
    * @param city
    * @param state
    * @return Instance of Person class with no middle name and without specified
    *    gender, employment status, or home ownership status.
    */
   public Person createPerson(
      final String lastName,
      final String firstName,
      final String streetAddress,
      final String city,
      final String state)
   {
      // implementation goes here...
   }
 
   /**
    * Generate and provide instance of Person class with no middle name and
    * with specified home ownership status. All instances of Person returned
    * from this method are assumed to be Female and to be Employed, but have no
    * address information.
    *
    * @param lastName
    * @param firstName
    * @param homeOwnerStatus
    * @return Instance of Person with provided first name, provided last name,
    *    and provided home ownership status, and assumed to be an employed
    *    female.
    */
   public Person createPerson(
      final String lastName,
      final String firstName,
      final boolean homeOwnerStatus)
   {
      // implementation goes here...
   }

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

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

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

Преимущества и преимущества

Перегрузка методов в Java проста для понимания и распространена в нескольких языках, включая C / C ++ и C # . Перегрузка метода особенно эффективна, когда параметры являются необязательными. Например, перегрузка метода, которая устранила ожидание передаваемого отчества, была гораздо более эффективной в моих примерах, чем перегрузка метода, делающая предположения о том, что конкретный экземпляр является занятой женщиной. Если характеристики отчества, пола и статуса занятости являются действительно необязательными, то не стоит принимать какое-либо значение для какого-либо из них, а лучше, чем предполагать конкретное значение для них.

Затраты и недостатки

Разумная перегрузка метода может быть полезной, но перегрузка метода должна использоваться осторожно. В разделе « Определение методов » урока « Классы и объекты» в разделе « Изучение языка Java» содержится предупреждение: «Перегруженные методы следует использовать с осторожностью, поскольку они могут сделать код гораздо менее читабельным».

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

Мой пример показал, что комментарии должны использоваться для объяснения предположений, сделанных перегруженными методами. Как только что упоминалось, они могут быть устаревшими или неточными или даже недоступными, если разработчик не удосужился их написать. Очевидно, было бы лучше иметь возможность называть методы по-разному, чтобы имя метода могло дать подсказки о его предположениях, а не полагаться исключительно на Javadoc. Использование именованных методов таким образом будет предметом более поздней публикации, но использование разных имен для методов по определению делает их больше не перегруженными методами.

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

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

Пользовательские типы включают улучшенную перегрузку метода / конструктора

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Person createPerson(
      final String lastName,
      final String firstName,
      final HomeownerStatus homeOwnership)
   {
      // implementation goes here...
   }
 
   public Person createPerson(
      final String lastName,
      final String firstName,
      final Gender gender)
   {
      // implementation goes here...
   }
 
   public Person createPerson(
      final String lastName,
      final String firstName,
      final EmploymentStatus employmentStatus)
   {
      // implementation goes here...
   }

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

Вывод

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