«1. Обзор

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

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

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

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

3. Реализация Builder в 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("baeldung")
  .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. Генерация Builder

FreeBuilder — это библиотека с открытым исходным кодом, которая помогает разработчикам избежать шаблонного кода при реализации классов Builder. Он использует обработку аннотаций в 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("baeldung")
  .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("baeldung")
  .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("baeldung")
  .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 Optional:

@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("baeldung")
  .setAge(10)
  .setPermanent(true)
  .build();

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

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

Хотя для обработки пустых значений в 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("baeldung")
  .setAge(10)
  .addAccessTokens(1221819L)
  .addAccessTokens(1223441L, 134567L)
  .build();

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

Employee employee = builder.setName("baeldung")
  .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 type:

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

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

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

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

Наряду с типами FreeBuilder, FreeBuilder также позволяет вкладывать другие построители, такие как protos.

10. Построение частичного объекта

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

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

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

Employee employee = builder.setName("baeldung")
  .setAge(10)
  .setEmail("[email protected]")
  .buildPartial();

assertNotNull(employee.getEmail());

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

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. Сравнение с другими библиотеками построителей

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

    Методы сопоставления Вложенные сборочные типы Частичные объекты

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

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

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

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