Lombok @Builder mit Vererbung

Lombok @Builder mit Vererbung

1. Überblick

Die Lombok-Bibliothek bietet eine großartige Möglichkeit,Builder Pattern zu implementieren, ohne einen Boilerplate-Code zu schreiben: die Annotation@Builder.

In diesem kurzen Tutorial lernen wir speziellhow to deal with the @Builder annotation when inheritance is involved. Wir werden zwei Techniken demonstrieren. Man verlässt sich auf Standardfunktionen von Lombok. Der andere nutzt eine experimentelle Funktion, die in Lombok 1.18 eingeführt wurde.

Für einen breiteren Überblick über die Builder-Annotation können wir aufUsing Lombok’s @Builder Annotation verweisen.

Ein detaillierter Blick auf die Project Lombok-Bibliothek ist auch inIntroduction to Project Lombok verfügbar.

2. Lombok@Builder und Vererbung

2.1. Das Problem definieren

Nehmen wir an, unsereChild-Klasse erweitert eineParent-Klasse:

@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;
}

Wenn Sie@Builder für eine Klasse verwenden, die eine andere Klasse wie diese erweitert, wird in der Anmerkung der folgende Kompilierungsfehler angezeigt:

Der implizite Superkonstruktor Parent () ist undefiniert. Muss explizit einen anderen Konstruktor aufrufen

Dies liegt daran, dass Lombok nicht die Felder der Oberklassen berücksichtigt, sondern nur die der aktuellen Klasse.

2.2. Lösung des Problems

Zum Glück gibt es eine einfache Problemumgehung. Wir können (mit unserer IDE oder sogar manuell) einen feldbasierten Konstruktor generieren. Dies schließt auch die Felder aus den Oberklassen ein. Wir kommentieren es mit@Builder anstelle der Klasse:

@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;
    }
}

Auf diese Weise können wir über die KlasseChildauf einen praktischen Builder zugreifen, mit dem wir auch die Felder der KlasseParentangeben können:

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. Mehrere@Builders koexistieren lassen

Falls die Oberklasse selbst mit@Builder annotiert ist, wird beim Annotieren des Konstruktors der KlasseChild der folgende Fehler angezeigt:

Der Rückgabetyp ist mit Parent.builder () nicht kompatibel.

Dies liegt daran, dass die KlasseChildversucht, beideBuilders mit demselben Namen verfügbar zu machen.

Wir können dieses Problem beheben, indem wir mindestens einer der Buildermethoden einen eindeutigen Namen zuweisen:

@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;
    }
}

Wir können dannParentBuilder bisChild.builder() undChildBuilder bisChild.childBuilder() erhalten.

2.4. Unterstützung größerer Vererbungshierarchien

In einigen Fällen müssen wir möglicherweise tiefere Vererbungshierarchien unterstützen. Wir können dasselbe Muster wie zuvor verwenden. Erstellen wir eine Unterklasse vonChild.

@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;
    }
}

Wie zuvor müssen wir manuell einen Konstruktor hinzufügen. Dies muss alle Eigenschaften aller übergeordneten Klassen und des untergeordneten als Argumente akzeptieren. Wir fügen dann die Annotation@Builderwie zuvor hinzu. Durch Angabe eines anderen eindeutigen Methodennamens in der Annotation können Builder fürParent,Child oderStudent erhalten werden.

Student student = Student.studentBuilder()
  .parentName("Andrea")
  .parentAge(38)
  .childName("Emma")
  .childAge(6)
  .schoolName("example 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("example High School");

Wir können dieses Muster also erweitern, um mit jeder Vererbungstiefe fertig zu werden. Der Konstruktor, den wir erstellen müssen, kann ziemlich groß werden, aber Ihre IDE kann Ihnen dabei helfen.

3. Lombok@SuperBuilder und Vererbung

Wie bereits erwähnt,version 1.18 of Lombok introduced the @SuperBuilder annotation. Wir können dies verwenden, um unser Problem auf einfachere Weise zu lösen.

3.1. Anwenden der Anmerkungen

Wir können einen Builder erstellen, der die Eigenschaften seiner Vorfahren sehen kann.

Dazu kommentieren wir unsere Klasse und ihre Vorfahren mit der Annotation@SuperBuilder.

Lassen Sie uns hier unsere dreistufige Hierarchie demonstrieren. Beachten Sie, dass das Prinzip für die einfache Vererbung von Eltern und Kindern dasselbe ist:

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

Wenn alle Klassen auf diese Weise mit Anmerkungen versehen sind, erhalten wir einen Builder für die untergeordnete Klasse, der auch die Eigenschaften der Eltern verfügbar macht.

Beachten Sie, dass wir alle Klassen mit Anmerkungen versehen müssen. @SuperBuilder cannot be mixed with @Builder within the same class hierarchy. Dies führt zu einem Kompilierungsfehler.

3.2. Verwenden des Builders

This time, we don’t need to define any special constructors. Die von@SuperBuilder generierte Builder-Klasse verhält sich genauso wie die, die wir mit dem Haupt-Lombok@Builder generiert haben:

Student student = Student.builder()
  .parentName("Andrea")
  .parentAge(38)
  .childName("Emma")
  .childAge(6)
  .schoolName("example 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("example High School");

4. Fazit

Wir haben gesehen, wie wir mit den häufigen Fallstricken umgehen können, wenn die Annotation@Builderin Klassen verwendet wird, die Vererbung verwenden.

Wenn wir die Hauptanmerkung von Lombok@Builderverwenden, müssen wir einige zusätzliche Schritte ausführen, damit dies funktioniert. Wenn wir jedoch bereit sind, die experimentellen Funktionen zu verwenden, kann@SuperBuilder die Dinge vereinfachen.

Wie immer ist der vollständige Quellcodeover on Github verfügbar.