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

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

1. Вступление

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

Обязательно ознакомьтесь с нашимиintro to Lombok.

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

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


    org.projectlombok
    lombok
    1.16.18
    provided

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());
}

Of course, this test fails since the @Builder annotation doesn’t populate values. Скоро мы это исправим.

Если мы используем 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. Аннотация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;
}

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

При этом значения по умолчанию будут присутствовать в компоновщике, что делает первый тестовый пример успешным. Unfortunately, the no-args constructor won’t get the default values, making the second test case fail. Даже если конструктор без аргументов не сгенерирован, а явно написан.

Этот побочный эффект аннотации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;
    }
}

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

К сожалению, цена за дублирование кода. For a POJO with tens of fields, it could be error prone to maintain the double initialization.с

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

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

// 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 also поддерживает создание экземпляра построителя из экземпляра исходного класса. Эта функция не включена по умолчанию. Мы можем включить его, установив параметрtoBuilder в аннотации конструктора:

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

При этомwe can get rid of the double initialization.

Конечно, за это есть цена. We have to instantiate the class to create a builder. Итак, нам нужно изменить и наши тесты:

@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.

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

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