Статьи

NetBeans 7.2: Рефакторинг параметризованного конструктора как построителя

Вскоре после того , как второе издание из Джоша Блох «s Effective Java было выпущено, я позаимствовал копию своего коллеги , чтобы просмотреть его и рассмотреть возможность покупки копии моих собственные , несмотря на уже владеющий экземпляр первого издания. Мне не потребовалось много времени, чтобы понять, что Второе издание стоит того, чтобы его купить. В частности, меня поразила полезность первого нового элемента во втором издании (пункт № 2 «Рассмотрим конструктор, когда он сталкивается со многими параметрами конструктора»). Этот пункт решает многие проблемы, с которыми я столкнулся во время разработки Java. В этой статье я расскажу о подходе, описанном в этой главе, и о том, как NetBeans 7.2 обеспечивает поддержку рефакторинга для этого подхода.

Как указано в пункте № 2 второго издания Effective Java , использование конструкторов с большими списками параметров имеет ряд недостатков. Чтобы проиллюстрировать это, я привожу листинг кода ниже.

Employee.java

package dustin.examples;

/**
 * Simple employee class intended to illustrate NetBeans 7.2 and refactoring
 * constructor to use Builder pattern as discussed in Item #2 of the Second
 * Edition of Joshua Bloch's <em>Effective Java</em>.
 * 
 * @author Dustin
 */
public class Employee 
{
   private String lastName;

   private String middleName;

   private String firstName;

   private long id;

   private int birthYear;

   private int birthMonth;

   private int birthDate;

   private int hireYear;

   private int hireMonth;

   private int hireDate;

   public Employee(
      final String newLastName,
      final String newMiddleName,
      final String newFirstName,
      final long newId,
      final int newBirthYear,
      final int newBirthMonth,
      final int newBirthDate,
      final int newHireYear,
      final int newHireMonth,
      final int newHireDate)
   {
      this.lastName = newLastName;
      this.middleName = newMiddleName;
      this.firstName = newFirstName;
      this.id = newId;
      this.birthYear = newBirthYear;
      this.birthMonth = newBirthMonth;
      this.birthDate = newBirthDate;
      this.hireYear = newHireYear;
      this.hireMonth = newHireMonth;
      this.hireDate = newHireDate;
   }
}

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

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

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

Элегантный подход, который позволяет клиенту предоставлять только необязательные параметры, которые имеют смысл, не предоставляя в противном случае ненужные методы установки, — это шаблон Builder. NetBeans 7.2 упрощает рефакторинг конструктора с помощью длинного списка параметров в приведенном выше примере, чтобы использовать преимущества экземпляра Builder. На следующем снимке экрана показано, как я могу использовать NetBeans 7.2 для этого, наведя курсор на параметризованный конструктор, щелкнув по нему правой кнопкой мыши и выбрав параметр «Заменить конструктор с помощью Builder …».

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

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

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

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

Кнопка «Предварительный просмотр» может быть нажата, чтобы увидеть, как будет выглядеть класс без фактического создания файла класса. При нажатии кнопки «Refactor» создается новый класс Java с именем пакета и класса, указанным в текстовом поле «Имя класса Builder». Результат в этом случае показан на следующем снимке экрана с сгенерированным кодом, перечисленным в списке после снимка.

NetBeans 7.2-Generated EmployeeBuilder.java

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dustin.examples;


public class EmployeeBuilder
{
   private String newLastName;
   private String newMiddleName = null;
   private String newFirstName;
   private long newId;
   private int newBirthYear = 0;
   private int newBirthMonth = 0;
   private int newBirthDate = 0;
   private int newHireYear = 0;
   private int newHireMonth = 0;
   private int newHireDate = 0;

   public EmployeeBuilder()
   {
   }

   public EmployeeBuilder setNewLastName(String newLastName)
   {
      this.newLastName = newLastName;
      return this;
   }

   public EmployeeBuilder setNewMiddleName(String newMiddleName)
   {
      this.newMiddleName = newMiddleName;
      return this;
   }

   public EmployeeBuilder setNewFirstName(String newFirstName)
   {
      this.newFirstName = newFirstName;
      return this;
   }

   public EmployeeBuilder setNewId(long newId)
   {
      this.newId = newId;
      return this;
   }

   public EmployeeBuilder setNewBirthYear(int newBirthYear)
   {
      this.newBirthYear = newBirthYear;
      return this;
   }

   public EmployeeBuilder setNewBirthMonth(int newBirthMonth)
   {
      this.newBirthMonth = newBirthMonth;
      return this;
   }

   public EmployeeBuilder setNewBirthDate(int newBirthDate)
   {
      this.newBirthDate = newBirthDate;
      return this;
   }

   public EmployeeBuilder setNewHireYear(int newHireYear)
   {
      this.newHireYear = newHireYear;
      return this;
   }

   public EmployeeBuilder setNewHireMonth(int newHireMonth)
   {
      this.newHireMonth = newHireMonth;
      return this;
   }

   public EmployeeBuilder setNewHireDate(int newHireDate)
   {
      this.newHireDate = newHireDate;
      return this;
   }

   public Employee createEmployee()
   {
      return new Employee(newLastName, newMiddleName, newFirstName, newId, newBirthYear, newBirthMonth, newBirthDate, newHireYear, newHireMonth, newHireDate);
   }
   
}

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

Адаптированный EmployeeBuilder.java

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dustin.examples;


public class EmployeeBuilder
{
   private String newLastName;
   private String newMiddleName = null;
   private String newFirstName;
   private long newId;
   private int newBirthYear = 0;
   private int newBirthMonth = 0;
   private int newBirthDate = 0;
   private int newHireYear = 0;
   private int newHireMonth = 0;
   private int newHireDate = 0;

   public EmployeeBuilder(
      final String newBldrLastName, final String newBldrFirstName, final String newBldrMiddleName)
   {
      this.newLastName = newBldrLastName;
      this.newFirstName = newBldrFirstName;
      this.newMiddleName = newBldrMiddleName;
   }

   public EmployeeBuilder setNewLastName(String newLastName)
   {
      this.newLastName = newLastName;
      return this;
   }

   public EmployeeBuilder setNewMiddleName(String newMiddleName)
   {
      this.newMiddleName = newMiddleName;
      return this;
   }

   public EmployeeBuilder setNewFirstName(String newFirstName)
   {
      this.newFirstName = newFirstName;
      return this;
   }

   public EmployeeBuilder setNewId(long newId)
   {
      this.newId = newId;
      return this;
   }

   public EmployeeBuilder setNewBirthYear(int newBirthYear)
   {
      this.newBirthYear = newBirthYear;
      return this;
   }

   public EmployeeBuilder setNewBirthMonth(int newBirthMonth)
   {
      this.newBirthMonth = newBirthMonth;
      return this;
   }

   public EmployeeBuilder setNewBirthDate(int newBirthDate)
   {
      this.newBirthDate = newBirthDate;
      return this;
   }

   public EmployeeBuilder setNewHireYear(int newHireYear)
   {
      this.newHireYear = newHireYear;
      return this;
   }

   public EmployeeBuilder setNewHireMonth(int newHireMonth)
   {
      this.newHireMonth = newHireMonth;
      return this;
   }

   public EmployeeBuilder setNewHireDate(int newHireDate)
   {
      this.newHireDate = newHireDate;
      return this;
   }

   public Employee createEmployee()
   {
      return new Employee(newLastName, newMiddleName, newFirstName, newId, newBirthYear, newBirthMonth, newBirthDate, newHireYear, newHireMonth, newHireDate);
   }
   
}

Код, используемый для вызова адаптированного компоновщика, показанного в последнем листинге кода, иллюстрируется в следующем листинге кода. Этот листинг кода демонстрирует, насколько читаемый («свободно» в более модном языке) клиентский код находится в компоновщике, и демонстрирует, что объект никогда не находится в частичном или несовместимом состоянии.

package dustin.examples;

import java.util.Calendar;

/**
 * Example calling adapted employee builder to get an instance of employee.
 *
 * @author Dustin
 */
public class Main
{
   public static void main(final String[] arguments)
   {
      final Employee employee =
         new EmployeeBuilder("Coyote", "Wile", "E.")
              .setNewBirthMonth(Calendar.SEPTEMBER)
              .setNewBirthDate(17)
              .setNewBirthYear(1949)
              .createEmployee();
   }
}

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

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

Хотя класс построителя NetBeans 7.2 не совсем то, что я написал вручную, он довольно близок и действительно прост в использовании. Это позволяет легко использовать существующий параметризованный конструктор для быстрой генерации в значительной степени эквивалентного класса компоновщика. Хотя мне нужно немного отредактировать сгенерированный класс, чтобы получить предпочтительную реализацию, это все же экономит мое время. Кроме того, это еще один пример того, как возможности генерации кода в среде IDE помогают разработчику увидеть, как реализуется конкретная парадигма или шаблон.