Lombok Builder mit Standardwert

Lombok Builder mit Standardwert

1. Einführung

In diesem kurzen Tutorial untersuchen wir, wie wir Standardwerte für Attribute bereitstellen können, wenn das Builder-Muster mitLombok verwendet wird.

Schauen Sie sich auch unsereintro to Lombokan.

2. Abhängigkeiten

In diesem Lernprogramm werden wirLombok verwenden. Dafür benötigen wir nur eine Abhängigkeit:


    org.projectlombok
    lombok
    1.16.18
    provided

3. POJO mit Lombok Builder

Schauen wir uns zunächst an, wie Lombok uns dabei helfen kann, den für die Implementierung des Builder-Musters erforderlichen Boilerplate-Code zu entfernen.

Wir beginnen mit einem einfachen POJO:

public class Pojo {
    private String name;
    private boolean original;
}

Damit diese Klasse nützlich ist, benötigen wir Getter. Wenn wir diese Klasse beispielsweise mit einem ORM verwenden möchten, benötigen wir wahrscheinlich einen Standardkonstruktor.

Darüber hinaus möchten wir einen Builder für diese Klasse. Mit Lombok können wir all dies mit ein paar einfachen Anmerkungen erreichen:

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Pojo {
    private String name;
    private boolean original;
}

4. Erwartungen definieren

Definieren wir einige Erwartungen für das, was wir in Form von Unit-Tests erreichen wollen.

Die erste und grundlegende Voraussetzung ist das Vorhandensein von Standardwerten, nachdem wir ein Objekt mit einem Builder erstellt haben:

@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. Wir werden das bald beheben.

Wenn wir ein ORM verwenden, stützt es sich normalerweise auf einen Standardkonstruktor. Daher sollten wir vom Standardkonstruktor dasselbe Verhalten erwarten wie vom Builder:

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

Zu diesem Zeitpunkt besteht dieser Test.

Nun wollen wir sehen, wie wir beide Tests bestehen können!

5. LomboksBuilder.Default Anmerkung

Seit Lombok v1.16.16 können wir die innere Annotation von@Builderverwenden:

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

Es ist einfach und lesbar, weist jedoch einige Mängel auf.

Damit sind die Standardwerte beim Builder vorhanden, sodass der erste Testfall bestanden wird. Unfortunately, the no-args constructor won’t get the default values, making the second test case fail. Auch wenn der Konstruktor no-args nicht generiert, sondern explizit geschrieben wurde.

Dieser Nebeneffekt der Annotation vonBuilder.Defaultist von Anfang an vorhanden und wird wahrscheinlich noch lange bei uns bleiben.

6. Initialisieren Sie den Builder

Wir können versuchen, beide Tests zu bestehen, indem wir Standardwerte in einer minimalistischen Builder-Implementierung definieren:

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

Auf diese Weise bestehen beide Tests.

Leider ist der Preis Code-Vervielfältigung. For a POJO with tens of fields, it could be error prone to maintain the double initialization.

Wenn wir jedoch bereit sind, diesen Preis zu zahlen, sollten wir uns auch um eine weitere Sache kümmern. Wenn wir unsere Klasse mithilfe eines Refactorings in unserer IDE umbenennen, wird die statische innere Klasse nicht automatisch umbenannt. Dann wird Lombok es nicht finden und unser Code wird kaputt gehen.

Um dieses Risiko auszuschließen, können wir die Builder-Annotation dekorieren:

// 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. Verwenden vontoBuilder

@Builder also unterstützt das Generieren einer Instanz des Builders aus einer Instanz der ursprünglichen Klasse. Diese Funktion ist standardmäßig nicht aktiviert. Wir können es aktivieren, indem wir den ParametertoBuilder in der Builder-Annotation festlegen:

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

Damit istwe can get rid of the double initialization.

Natürlich gibt es dafür einen Preis. We have to instantiate the class to create a builder. Also müssen wir auch unsere Tests ändern:

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

Wiederum bestehen beide Tests, sodass wir mit dem Konstruktor no-args denselben Standardwert haben wie mit dem Builder.

8. Fazit

Daher haben wir uns verschiedene Optionen angesehen, um Standardwerte für den Lombok-Builder bereitzustellen.

Der Nebeneffekt der AnnotationBuilder.Default ist es wert, im Auge behalten zu werden. Aber auch die anderen Optionen haben ihre Nachteile. Wir müssen also sorgfältig auf der Grundlage der aktuellen Situation auswählen.

Wie immer ist der Codeover on GitHub verfügbar.