Перейти к основному содержимому

Автоматическое создание шаблона Builder с помощью FreeBuilder

· 9 мин. чтения

1. Обзор

В этом руководстве мы будем использовать библиотеку FreeBuilder для создания классов построителей на Java.

2. Шаблон проектирования "Строитель"

Builder — один из наиболее широко используемых шаблонов проектирования создания в объектно-ориентированных языках. Он абстрагирует создание сложного объекта предметной области и предоставляет гибкий API для создания экземпляра. Таким образом, это помогает поддерживать краткий уровень предметной области.

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

3. Реализация компоновщика на Java

Прежде чем мы продолжим работу с FreeBuilder, давайте реализуем шаблонный построитель для нашего класса Employee :

public class Employee {

private final String name;
private final int age;
private final String department;

private Employee(String name, int age, String department) {
this.name = name;
this.age = age;
this.department = department;
}
}

И внутренний класс Builder :

public static class Builder {

private String name;
private int age;
private String department;

public Builder setName(String name) {
this.name = name;
return this;
}

public Builder setAge(int age) {
this.age = age;
return this;
}

public Builder setDepartment(String department) {
this.department = department;
return this;
}

public Employee build() {
return new Employee(name, age, department);
}
}

Соответственно, теперь мы можем использовать конструктор для создания экземпляра объекта Employee :

Employee.Builder emplBuilder = new Employee.Builder();

Employee employee = emplBuilder
.setName("foreach")
.setAge(12)
.setDepartment("Builder Pattern")
.build();

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

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

4. Зависимость от Maven

Чтобы добавить библиотеку FreeBuilder, мы добавим зависимость FreeBuilder Maven в наш pom.xml :

<dependency>
<groupId>org.inferred</groupId>
<artifactId>freebuilder</artifactId>
<version>2.4.1</version>
</dependency>

5. Аннотация FreeBuilder

5.1. Генерация построителя

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

Мы аннотируем наш класс Employee из предыдущего раздела с помощью @ FreeBuilder и посмотрим, как он автоматически генерирует класс построителя:

@FreeBuilder
public interface Employee {

String name();
int age();
String department();

class Builder extends Employee_Builder {
}
}

Важно отметить, что теперь Employee является интерфейсом , а не классом POJO. Кроме того, он содержит все атрибуты объекта Employee в виде методов.

Прежде чем мы продолжим использовать этот конструктор, мы должны настроить наши IDE, чтобы избежать проблем с компиляцией. Поскольку FreeBuilder автоматически генерирует класс Employee_Builder во время компиляции, IDE обычно выдает ClassNotFoundException в строке номер 8 .

Чтобы избежать таких проблем, нам нужно включить обработку аннотаций в IntelliJ или Eclipse . При этом мы будем использовать процессор аннотаций FreeBuilder org.inferred.freebuilder.processor.Processor. Кроме того, каталог, используемый для создания этих исходных файлов, должен быть помечен как Generated Sources Root .

В качестве альтернативы мы также можем выполнить mvn install для сборки проекта и создания необходимых классов построителя.

Наконец, мы скомпилировали наш проект и теперь можем использовать класс Employee.Builder :

Employee.Builder builder = new Employee.Builder();

Employee employee = builder.name("foreach")
.age(10)
.department("Builder Pattern")
.build();

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

Мы увидим, как FreeBuilder обрабатывает необязательные атрибуты в следующем разделе.

Во-вторых, имена методов Employee.Builder не соответствуют соглашениям об именах JavaBean. Мы увидим это в следующем разделе.

5.2. Соглашение об именах JavaBean

Чтобы заставить FreeBuilder следовать соглашению об именах JavaBean, мы должны переименовать наши методы в Employee и добавить к методам префикс get :

@FreeBuilder
public interface Employee {

String getName();
int getAge();
String getDepartment();

class Builder extends Employee_Builder {
}
}

Это создаст геттеры и сеттеры, которые следуют соглашению об именах JavaBean:

Employee employee = builder
.setName("foreach")
.setAge(10)
.setDepartment("Builder Pattern")
.build();

5.3. Картографические методы

В сочетании с геттерами и сеттерами FreeBuilder также добавляет методы сопоставления в класс построителя. Эти методы преобразования принимают UnaryOperator в качестве входных данных, что позволяет разработчикам вычислять значения сложных полей.

Предположим, что в нашем классе Employee также есть поле зарплаты:

@FreeBuilder
public interface Employee {
Optional<Double> getSalaryInUSD();
}

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

long salaryInEuros = INPUT_SALARY_EUROS;
Employee.Builder builder = new Employee.Builder();

Employee employee = builder
.setName("foreach")
.setAge(10)
.mapSalaryInUSD(sal -> salaryInEuros * EUROS_TO_USD_RATIO)
.build();

FreeBuilder предоставляет такие методы сопоставления для всех полей.

6. Значения по умолчанию и проверки ограничений

6.1. Установка значений по умолчанию

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

Чтобы избежать таких сбоев, мы можем либо установить значения по умолчанию для полей, либо сделать их необязательными .

Мы можем установить значения по умолчанию в конструкторе Employee.Builder :

@FreeBuilder
public interface Employee {

// getter methods

class Builder extends Employee_Builder {

public Builder() {
setDepartment("Builder Pattern");
}
}
}

Поэтому мы просто устанавливаем отдел по умолчанию в конструкторе. Это значение будет применяться ко всем объектам Employee .

6.2. Проверки ограничений

Обычно у нас есть определенные ограничения на значения полей. Например, действительный адрес электронной почты должен содержать «@», или возраст Сотрудника должен быть в пределах допустимого диапазона.

Такие ограничения требуют от нас проверки входных значений. И FreeBuilder позволяет нам добавлять эти проверки, просто переопределяя методы `` установки :

@FreeBuilder
public interface Employee {

// getter methods

class Builder extends Employee_Builder {

@Override
public Builder setEmail(String email) {
if (checkValidEmail(email))
return super.setEmail(email);
else
throw new IllegalArgumentException("Invalid email");

}

private boolean checkValidEmail(String email) {
return email.contains("@");
}
}
}

7. Дополнительные значения

7.1. Использование необязательных полей

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

@FreeBuilder
public interface Employee {

String getName();
int getAge();

// other getters

Optional<Boolean> getPermanent();

Optional<String> getDateOfJoining();

class Builder extends Employee_Builder {
}
}

Теперь мы можем не указывать любое значение для необязательных полей:

Employee employee = builder.setName("foreach")
.setAge(10)
.setPermanent(true)
.build();

Примечательно, что мы просто передали значение постоянного поля вместо необязательного. Поскольку мы не установили значение для поля dateOfJoining , это будет Optional.empty() , которое используется по умолчанию для необязательных полей.

7.2. Использование полей @Nullable

Хотя для обработки null в Java рекомендуется использовать Optional , FreeBuilder позволяет нам использовать @Nullable для обратной совместимости : ** **

@FreeBuilder
public interface Employee {

String getName();
int getAge();

// other getter methods

Optional<Boolean> getPermanent();
Optional<String> getDateOfJoining();

@Nullable String getCurrentProject();

class Builder extends Employee_Builder {
}
}

Использование Optional в некоторых случаях не рекомендуется , что является еще одной причиной, по которой @Nullable предпочтительнее для классов строителей.

8. Коллекции и карты

FreeBuilder имеет специальную поддержку коллекций и карт:

@FreeBuilder
public interface Employee {

String getName();
int getAge();

// other getter methods

List<Long> getAccessTokens();
Map<String, Long> getAssetsSerialIdMapping();


class Builder extends Employee_Builder {
}
}

FreeBuilder добавляет удобные методы для добавления элементов ввода в коллекцию в классе построителя :

Employee employee = builder.setName("foreach")
.setAge(10)
.addAccessTokens(1221819L)
.addAccessTokens(1223441L, 134567L)
.build();

В классе построителя также есть метод getAccessTokens() , который возвращает неизменяемый список . Аналогично для карты:

Employee employee = builder.setName("foreach")
.setAge(10)
.addAccessTokens(1221819L)
.addAccessTokens(1223441L, 134567L)
.putAssetsSerialIdMapping("Laptop", 12345L)
.build();

Метод получения для Map также возвращает неизменяемую карту в клиентский код.

9. Вложенные строители

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

Например, предположим, что у нас есть вложенный сложный тип Address в классе Employee :

@FreeBuilder
public interface Address {

String getCity();

class Builder extends Address_Builder {
}
}

Теперь FreeBuilder генерирует методы установки , которые принимают Address.Builder в качестве входных данных вместе с типом адреса :

Address.Builder addressBuilder = new Address.Builder();
addressBuilder.setCity(CITY_NAME);

Employee employee = builder.setName("foreach")
.setAddress(addressBuilder)
.build();

Примечательно, что FreeBuilder также добавляет метод для настройки существующего объекта Address в Employee :

Employee employee = builder.setName("foreach")
.setAddress(addressBuilder)
.mutateAddress(a -> a.setPinCode(112200))
.build();

Наряду с типами FreeBuilder FreeBuilder также допускает вложение других сборщиков, таких как protos .

10. Создание частичного объекта

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

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

Чтобы ослабить такие ограничения, FreeBuilder позволяет нам создавать частичные объекты:

Employee employee = builder.setName("foreach")
.setAge(10)
.setEmail("abc@xyz.com")
.buildPartial();

assertNotNull(employee.getEmail());

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

11. Пользовательский метод toString()

С объектами-значениями нам часто нужно добавить пользовательскую реализацию toString() . FreeBuilder позволяет это через абстрактные классы:

@FreeBuilder
public abstract class Employee {

abstract String getName();

abstract int getAge();

@Override
public String toString() {
return getName() + " (" + getAge() + " years old)";
}

public static class Builder extends Employee_Builder{
}
}

Мы объявили Employee как абстрактный класс, а не как интерфейс, и предоставили собственную реализацию toString() .

12. Сравнение с другими библиотеками Builder

Реализация построителя, которую мы обсуждали в этой статье, очень похожа на реализацию Lombok , Immutables или любого другого процессора аннотаций . Однако есть несколько отличительных характеристик , о которых мы уже говорили:

  • Методы сопоставления

  • Вложенные сборочные типы

  • Частичные объекты

13. Заключение

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

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

Все примеры кода доступны на GitHub .