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 .