Статьи

Слишком много параметров в методах Java, часть 2: объект параметров

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

Как правило, не рекомендуется создавать объекты «мусорных ящиков», которые связывают несвязанные параметры, чье единственное отношение друг к другу заключается в том, что их необходимо передавать одному и тому же методу или конструктору. Однако, когда связанные параметры передаются в конструктор или метод как часть очень связного объекта, рефакторинг, известный как Ввести объект параметров, является хорошим решением. Использование этого рефакторинга описывается как «группировка [параметров], которые естественным образом сочетаются». Я продемонстрирую этот рефакторинг в этом посте.

Чтобы продемонстрировать полезность рефакторинга Introduce Parameter Object , давайте сначала рассмотрим пример из последнего поста, который использует многочисленные параметры String и boolean параметры в вызове метода.

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
/**
    * Instantiate a Person object.
    *
    * @param lastName
    * @param firstName
    * @param middleName
    * @param salutation
    * @param suffix
    * @param streetAddress
    * @param city
    * @param state
    * @param isFemale
    * @param isEmployed
    * @param isHomeOwner
    * @return
    */
   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
   }

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

С предлагаемыми приложениями рефакторинга Int вводить объект параметров ранее показанный вызов метода становится проще благодаря уменьшенному количеству параметров. Это показано в следующем листинге кода.

1
2
3
4
5
6
7
8
9
public Person createPerson(
      final FullName fullName,
      final Address address,
      final boolean isFemale,
      final boolean isEmployed,
      final boolean isHomeOwner)
   {
      return new Person();
   }

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

FullName.java (Простой)

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
package dustin.examples;
 
/**
 * Full name of a person.
 *
 * @author Dustin
 */
public final class FullName
{
   private final String lastName;
   private final String firstName;
   private final String middleName;
   private final String salutation;
   private final String suffix;
 
   public FullName(
      final String newLastName,
      final String newFirstName,
      final String newMiddleName,
      final String newSalutation,
      final String newSuffix)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.middleName = newMiddleName;
      this.salutation = newSalutation;
      this.suffix = newSuffix;
   }
 
   public String getLastName()
   {
      return this.lastName;
   }
 
   public String getFirstName()
   {
      return this.firstName;
   }
 
   public String getMiddleName()
   {
      return this.middleName;
   }
 
   public String getSalutation()
   {
      return this.salutation;
   }
 
   public String getSuffix()
   {
      return this.suffix;
   }
 
   @Override
   public String toString()
   {
      return  this.salutation + " " + this.firstName + " " + this.middleName
            + this.lastName + ", " + this.suffix;
   }
}

Address.java (простой)

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
package dustin.examples;
 
/**
 * Representation of a United States address.
 *
 * @author Dustin
 */
public final class Address
{
   private final String streetAddress;
   private final String city;
   private final String state;
 
   public Address(final String newStreetAddress, final String newCity, final String newState)
   {
      this.streetAddress = newStreetAddress;
      this.city = newCity;
      this.state = newState;
   }
 
   public String getStreetAddress()
   {
      return this.streetAddress;
   }
 
   public String getCity()
   {
      return this.city;
   }
 
   public String getState()
   {
      return this.state;
   }
 
   @Override
   public String toString()
   {
      return this.streetAddress + ", " + this.city + ", " + this.state;
   }
}

Хотя код улучшен, все еще есть некоторые проблемы, которые можно улучшить. В частности, оригинальный метод со слишком большим количеством параметров по-прежнему имеет три boolean параметра, которые можно легко спутать друг с другом. Хотя параметры String для этого метода были разделены на два новых класса, эти два новых класса по-прежнему состоят из набора String . В этих случаях может потребоваться добавить рефакторинг объекта « Представить параметр» с использованием пользовательских типов. Используя пользовательские типы, которые я показал в моем предыдущем посте, метод со слишком большим количеством параметров теперь выглядит так, как показано в следующем листинге кода.

1
2
3
4
5
6
7
8
9
public Person createPerson(
      final FullName fullName,
      final Address address,
      final Gender gender,
      final EmploymentStatus employment,
      final HomeownerStatus homeownerStatus)
   {
      // implementation goes here
   }

У метода теперь меньше параметров, и все параметры имеют разные типы. Среды IDE и компилятор Java теперь могут быть особенно полезны для обеспечения правильного использования клиентами этого интерфейса. Применение пользовательских типов (написанных в последнем посте) к FullName и Address приводит к следующим двум новым спискам кода для этих классов.

FullName.java (пользовательские типы)

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
package dustin.examples;
 
/**
 * Full name of a person.
 *
 * @author Dustin
 */
public final class FullName
{
   private final Name lastName;
   private final Name firstName;
   private final Name middleName;
   private final Salutation salutation;
   private final Suffix suffix;
 
   public FullName(
      final Name newLastName,
      final Name newFirstName,
      final Name newMiddleName,
      final Salutation newSalutation,
      final Suffix newSuffix)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.middleName = newMiddleName;
      this.salutation = newSalutation;
      this.suffix = newSuffix;
   }
 
   public Name getLastName()
   {
      return this.lastName;
   }
 
   public Name getFirstName()
   {
      return this.firstName;
   }
 
   public Name getMiddleName()
   {
      return this.middleName;
   }
 
   public Salutation getSalutation()
   {
      return this.salutation;
   }
 
   public Suffix getSuffix()
   {
      return this.suffix;
   }
 
   @Override
   public String toString()
   {
      return  this.salutation + " " + this.firstName + " " + this.middleName
            + this.lastName + ", " + this.suffix;
   }
}

Address.java (пользовательские типы)

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
package dustin.examples;
 
/**
 * Representation of a United States address.
 *
 * @author Dustin
 */
public final class Address
{
   private final StreetAddress streetAddress;
   private final City city;
   private final State state;
 
   public Address(final StreetAddress newStreetAddress, final City newCity, final State newState)
   {
      this.streetAddress = newStreetAddress;
      this.city = newCity;
      this.state = newState;
   }
 
   public StreetAddress getStreetAddress()
   {
      return this.streetAddress;
   }
 
   public City getCity()
   {
      return this.city;
   }
 
   public State getState()
   {
      return this.state;
   }
 
   @Override
   public String toString()
   {
      return this.streetAddress + ", " + this.city + ", " + this.state;
   }
}

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

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

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

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

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

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

Вывод

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