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

Lombok Builder со значением по умолчанию

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

1. Введение

В этом кратком руководстве мы рассмотрим, как мы можем предоставить значения по умолчанию для атрибутов при использовании шаблона построителя с Lombok .

Обязательно ознакомьтесь с нашим введением в Ломбок .

2. Зависимости

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

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>

3. POJO с Lombok Builder

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

Мы начнем с простого POJO:

public class Pojo {
private String name;
private boolean original;
}

Чтобы этот класс был полезен, нам понадобятся геттеры. Кроме того, например, если мы хотим использовать этот класс с ORM, нам, вероятно, понадобится конструктор по умолчанию.

Кроме того, нам нужен строитель для этого класса. С Lombok мы можем получить все это с помощью нескольких простых аннотаций:

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Pojo {
private String name;
private boolean original;
}

4. Определение ожиданий

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

Первое и основное требование — это наличие значений по умолчанию после того, как мы построим объект с помощью билдера:

@Test
public void givenBuilderWithDefaultValue_ThanDefaultValueIsPresent() {
Pojo build = Pojo.builder()
.build();
Assert.assertEquals("foo", build.getName());
Assert.assertTrue(build.isOriginal());
}

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

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

@Test
public void givenBuilderWithDefaultValue_NoArgsWorksAlso() {
Pojo build = Pojo.builder()
.build();
Pojo pojo = new Pojo();
Assert.assertEquals(build.getName(), pojo.getName());
Assert.assertTrue(build.isOriginal() == pojo.isOriginal());
}

На данном этапе этот тест проходит.

Теперь давайте посмотрим, как мы можем пройти оба теста!

5. Аннотация Lombok's Builder.Default

Начиная с Lombok v1.16.16, мы можем использовать внутреннюю аннотацию @Builder :

// class annotations as before
public class Pojo {
@Builder.Default
private String name = "foo";
@Builder.Default
private boolean original = true;
}

Он простой и читаемый, но имеет некоторые недостатки.

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

Этот побочный эффект аннотации Builder.Default присутствует с самого начала и, вероятно, будет с нами еще долго.

6. Инициализируйте построитель

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

// class annotations as before
public class Pojo {
private String name = "foo";
private boolean original = true;

public static class PojoBuilder {
private String name = "foo";
private boolean original = true;
}
}

Таким образом, оба теста пройдут.

К сожалению, цена дублирования кода. Для POJO с десятками полей поддержание двойной инициализации может быть подвержено ошибкам.

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

Чтобы устранить этот риск, мы можем украсить аннотацию построителя:

// class annotations as before
@Builder(builderClassName = "PojoBuilder")
public class Pojo {
private String name = "foo";
private boolean original = true;

public static class PojoBuilder {
private String name = "foo";
private boolean original = true;
}
}

7. Использование toBuilder

@Builder также поддерживает создание экземпляра построителя из экземпляра исходного класса. Эта функция не включена по умолчанию. Мы можем включить его, установив параметр toBuilder в аннотации построителя:

// class annotations as before
@Builder(toBuilder = true)
public class Pojo {
private String name = "foo";
private boolean original = true;
}

Благодаря этому мы можем избавиться от двойной инициализации .

Конечно, за это есть цена. Мы должны создать экземпляр класса для создания построителя. Итак, мы также должны изменить наши тесты:

@Test
public void givenBuilderWithDefaultValue_ThenDefaultValueIsPresent() {
Pojo build = new Pojo().toBuilder()
.build();
Assert.assertEquals("foo", build.getName());
Assert.assertTrue(build.isOriginal());
}

@Test
public void givenBuilderWithDefaultValue_thenNoArgsWorksAlso() {
Pojo build = new Pojo().toBuilder()
.build();
Pojo pojo = new Pojo();
Assert.assertEquals(build.getName(), pojo.getName());
Assert.assertTrue(build.isOriginal() == pojo.isOriginal());
}

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

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

Итак, мы рассмотрели несколько вариантов предоставления значений по умолчанию для построителя Lombok.

Побочный эффект Строителя . За аннотацией по умолчанию стоит следить. Но и у других вариантов есть свои недостатки. Поэтому мы должны тщательно выбирать, исходя из текущей ситуации.

Как всегда, код доступен на GitHub .