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 .