1. Обзор
Библиотека Lombok предоставляет отличный способ реализовать шаблон Builder
без написания шаблонного кода: аннотацию @Builder
.
В этом коротком руководстве мы специально узнаем , как работать с аннотацией @Builder,
когда речь идет о наследовании . Мы продемонстрируем две техники. Один полагается на стандартные функции Lombok. Другой использует экспериментальную функцию, представленную в Lombok 1.18.
Для более широкого обзора аннотации Builder мы можем обратиться к Использование аннотации Lombok @Builder
.
Подробный обзор библиотеки Project Lombok также доступен в разделе Introduction to Project Lombok .
2. Ломбок @Builder
и наследование
2.1. Определение проблемы
Предположим, что наш дочерний
класс расширяет родительский
класс:
@Getter
@AllArgsConstructor
public class Parent {
private final String parentName;
private final int parentAge;
}
@Getter
@Builder
public class Child extends Parent {
private final String childName;
private final int childAge;
}
При использовании @Builder
в классе, который расширяет другой подобный класс, мы получим следующую ошибку компиляции в аннотации:
Неявный суперконструктор Parent() не определен. Должен явно вызывать другой конструктор
Это связано с тем, что Lombok не учитывает поля суперклассов, а только поля текущего класса.
2.2. Решение проблемы
К счастью для нас, есть простой обходной путь. Мы можем сгенерировать (с помощью нашей IDE или даже вручную) конструктор на основе полей. Сюда входят также поля из суперклассов. Мы аннотируем его @Builder
вместо класса:
@Getter
@AllArgsConstructor
public class Parent {
private final String parentName;
private final int parentAge;
}
@Getter
public class Child extends Parent {
private final String childName;
private final int childAge;
@Builder
public Child(String parentName, int parentAge, String childName, int childAge) {
super(parentName, parentAge);
this.childName = childName;
this.childAge = childAge;
}
}
Таким образом, мы сможем получить доступ к удобному конструктору из класса Child
, который позволит нам указать также поля родительского класса:
Child child = Child.builder()
.parentName("Andrea")
.parentAge(38)
.childName("Emma")
.childAge(6)
.build();
assertThat(child.getParentName()).isEqualTo("Andrea");
assertThat(child.getParentAge()).isEqualTo(38);
assertThat(child.getChildName()).isEqualTo("Emma");
assertThat(child.getChildAge()).isEqualTo(6);
2.3. Сосуществование нескольких @Builder
s
Если сам суперкласс аннотирован с помощью @Builder
, мы получим следующую ошибку при аннотировании конструктора дочернего
класса:
> Тип возвращаемого значения несовместим с Parent.builder().
Это связано с тем, что класс Child
пытается предоставить доступ к обоим Builder
с одним и тем же именем.
Мы можем решить эту проблему, назначив уникальное имя хотя бы одному из методов компоновщика:
@Getter
public class Child extends Parent {
private final String childName;
private final int childAge;
@Builder(builderMethodName = "childBuilder")
public Child(String parentName, int parentAge, String childName, int childAge) {
super(parentName, parentAge);
this.childName = childName;
this.childAge = childAge;
}
}
Затем мы сможем получить ParentBuilder
через Child.builder()
и ChildBuilder
через Child.childBuilder()
.
2.4. Поддержка больших иерархий наследования
В некоторых случаях может потребоваться поддержка более глубоких иерархий наследования. Мы можем использовать тот же шаблон, что и раньше. Давайте создадим подкласс Child
.
@Getter
public class Student extends Child {
private final String schoolName;
@Builder(builderMethodName = "studentBuilder")
public Student(String parentName, int parentAge, String childName, int childAge, String schoolName) {
super(parentName, parentAge, childName, childAge);
this.schoolName = schoolName;
}
}
Как и раньше, нам нужно вручную добавить конструктор. Это должно принимать все свойства из всех родительских классов и дочерних в качестве аргументов. Затем мы добавляем аннотацию @Builder
, как и раньше. Указав другое уникальное имя метода в аннотации, мы можем получить построители для Parent
, Child
или Student
.
Student student = Student.studentBuilder()
.parentName("Andrea")
.parentAge(38)
.childName("Emma")
.childAge(6)
.schoolName("ForEach High School")
.build();
assertThat(student.getChildName()).isEqualTo("Emma");
assertThat(student.getChildAge()).isEqualTo(6);
assertThat(student.getParentName()).isEqualTo("Andrea");
assertThat(student.getParentAge()).isEqualTo(38);
assertThat(student.getSchoolName()).isEqualTo("ForEach High School");
Затем мы можем расширить этот шаблон, чтобы иметь дело с любой глубиной наследования. Конструктор, который нам нужно создать, может стать довольно большим, но ваша IDE может вам помочь.
3. Ломбок @SuperBuilder
и наследование
Как мы отмечали ранее, в версии 1.18 Lombok появилась аннотация @SuperBuilder
. Мы можем использовать это, чтобы решить нашу проблему более простым способом.
3.1. Применение аннотаций
Мы можем создать строитель, который может видеть свойства своих предков.
Для этого мы аннотируем наш класс и его предков аннотацией @SuperBuilder
.
Давайте продемонстрируем здесь нашу трехуровневую иерархию. Обратите внимание, что принцип простого родительского и дочернего наследования одинаков:
@Getter
@SuperBuilder
public class Parent {
// same as before...
@Getter
@SuperBuilder
public class Child extends Parent {
// same as before...
@Getter
@SuperBuilder
public class Student extends Child {
// same as before...
Когда все классы аннотированы таким образом, мы получаем построитель для дочернего класса, который также предоставляет свойства родителей.
Обратите внимание, что мы должны аннотировать все классы. @SuperBuilder
нельзя смешивать с @Builder
в рамках одной иерархии классов. Это приведет к ошибке компиляции.
3.2. Использование конструктора
На этот раз нам не нужно определять никаких специальных конструкторов. Класс построителя, сгенерированный @SuperBuilder
, ведет себя точно так же, как тот, который мы создали с помощью основного Lombok @Builder
:
Student student = Student.builder()
.parentName("Andrea")
.parentAge(38)
.childName("Emma")
.childAge(6)
.schoolName("ForEach High School")
.build();
assertThat(student.getChildName()).isEqualTo("Emma");
assertThat(student.getChildAge()).isEqualTo(6);
assertThat(student.getParentName()).isEqualTo("Andrea");
assertThat(student.getParentAge()).isEqualTo(38);
assertThat(student.getSchoolName()).isEqualTo("ForEach High School");
4. Вывод
Мы увидели, как справляться с распространенными ловушками при использовании аннотации @Builder
в классах, использующих наследование.
Если мы используем основную аннотацию Lombok @Builder
, у нас есть несколько дополнительных шагов, чтобы заставить ее работать. Но если мы хотим использовать экспериментальные функции, то @SuperBuilder
может все упростить.
Как всегда, полный исходный код доступен на Github .