Builder в одну строку

Шаблон проектирования Builder, цитирую, «отделяет конструирование сложного объекта от его представления, так что в результате одного и того же процесса конструирования могут получаться разные представления.» На практике это означает, что пользоватся builder обычно удобно, а вот реализовывать его — адов геморрой.

@Builder

В project lombok реализация Builder для какого-либо класса делается одной строкой:

/**
* Sample address entity.
*/
@Value
@Builder(toBuilder = true)
public class Address {
    /**
     * City name.
     */
    String city;


    /**
     * Street name.
     */
    String street;


    /**
     * Building name.
     */
    String building;
}

@Builder делает кучу вещей: создаёт статический метод builder(); генерирует внутренний класс, реализующий Builder; генерирует код этого класса, устанавливающий фактические значения; генерирует для него метод toString(); и, наконец, генерирует метод build(), создающий ваш класс.  Кроме того, поведение аннотации @Builder можно настраивать:

  • Параметр builderClassName задаёт имя внутреннего класса (по умолчанию «конструируемый тип+Builder»).
  • Параметр buildMethodName задаёт имя метода, создающего ваш класс (по умолчанию build())
  • Параметр builderMethodName задаёт имя статического метода, возвращающего Builder (по умолчанию builder())
  • Параметр toBuilder=true генерирует метод toBuilder(). Метод toBuilder() позволяет построить Builder из уже существующего класса, который будет инициализирован данными класса. По умолчанию такой метод не создаётся.

Использовать автоматически сгеренированный Builder проще простого:

public class AddressTest {


    @Test
    public void testBuildAddress() {
        Address testedObject = Address
                .builder()
                .city("Dublin")
                .street("O'Connell Street")
                .building("General Post Office")
                .build();


        assertThat(testedObject.getCity(), is("Dublin"));
        assertThat(testedObject.getStreet(), is("O'Connell Street"));
        assertThat(testedObject.getBuilding(), is("General Post Office"));


        System.out.println(testedObject.toString());
    }


    @Test
    public void testToBuilder() {
        Address sourceObject = Address
                .builder()
                .city("Dublin")
                .street("O'Connell Street")
                .building("General Post Office")
                .build();


        Address testedObject = sourceObject.toBuilder()
                .building("Belvedere House")
                .build();


        assertThat(testedObject.getCity(), is("Dublin"));
        assertThat(testedObject.getStreet(), is("O'Connell Street"));
        assertThat(testedObject.getBuilding(), is("Belvedere House"));


        System.out.println(testedObject.toString());
    }
}
Address(city=Dublin, street=O'Connell Street, building=General Post Office)
Address(city=Dublin, street=O'Connell Street, building=Belvedere House)

@Singular

Аннотация @Singular упрощает строительство коллекций. Если поле аннотировано @Singular, для него будет сформировано два метода добавления в коллекцию: один метод принимает единственный элемент будущей коллекции и добавляет его к ней, второй принимает коллекцию и добавляет её к будущей коллекции. Методов для замены будущей коллекции не будет сгенерировано.

@Singular работает только с некоторыми типами коллекций, как то:

  • Iterable, Collection, List
  • Set, SortedSet, NavigableSet
  • Map, SortedMap, NavigableMap
  • ImmutableCollection, ImmutableList
  • ImmutableSet, ImmutableSortedSet
  • ImmutableMap, ImmutableBiMap, ImmutableSortedMap

Кроме того, аннотация @Singular анализирует имя переменной и пытается его перевести из формы множественного числа, в форму единственного числа по правилам английского языка. Например, если коллекция называется adresses, то будет сгенерировано два метода: address для одного аргумента и addresses для коллекции. В случае, если lombok не сможет перевести имя переменной из множественного числа в единственное, необходимо будет задать имя метода для одного аргумента явно в параметре аннотации.

/**
* Sample person entity.
*/
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder
public class Person {


    /**
     * Some id;
     */
    @NonNull
    Integer id;


    /**
     * Person name
     */
    String name;


    /**
     * Person's addresses.
     */
    @Singular
    List<Address> addresses;
}
public class PersonTest {


    @Test(expected = NullPointerException.class)
    public void testCantNull() {
        Person testedObject = Person
                .builder()
                .name("Test von Testoff")
                .build();
    }


    @Test
    public void testTwoAddresses() {
        val addr1 = Address
                .builder()
                .city("Dublin")
                .street("O'Connell Street")
                .building("General Post Office")
                .build();


        val addr2 = addr1.toBuilder()
                .building("Belvedere House")
                .build();


        Person testedObject = Person
                .builder()
                .id(1)
                .name("Test von Testoff")
                .address(addr1)
                .address(addr2)
                .build();


        assertThat(testedObject.getAddresses().size(), is(2));


        System.out.println(testedObject.toString());
    }
}
Person(id=1, name=Test von Testoff, addresses=[Address(city=Dublin, street=O'Connell Street, building=General Post Office), Address(city=Dublin, street=O'Connell Street, building=Belvedere House)])

Как видно из примера, @Builder не позволяет создавать объекты с незаполненными @NonNull полями.

В этот же тесте я использова другую функциональность lombok: ключевое слово val. val как бы говорит компилятору: «Эй, ты там ведь сам всё равно знаешь, какой-там тип у данных, так не выноси мне мозг и сделай переменную именно того типа, который нужен». Другими словами, val позволяет определить локальную переменную не указывая её типа, в случае, если она сразу будет проинициализирована. Причём переменная будет объявлена  final. val нельзя использовать для определения полей класса, параметров функций итд.

Скачать код примера

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *