Lombok Builder con valore predefinito

1. Introduzione

In questo rapido tutorial, esamineremo come possiamo fornire valori predefiniti per gli attributi quando si utilizza il modello builder con Lombok .

Assicurati di dare un'occhiata anche alla nostra introduzione a Lombok.

2. Dipendenze

Useremo Lombok in questo tutorial e per questo abbiamo bisogno di una sola dipendenza:

 org.projectlombok lombok 1.18.10 provided 

3. POJO con Lombok Builder

Per prima cosa, diamo un'occhiata a come Lombok può aiutarci a sbarazzarci del codice boilerplate necessario per implementare il pattern builder.

Inizieremo con un semplice POJO:

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

Perché questa classe sia utile, avremo bisogno di getter. Inoltre, ad esempio, se desideriamo utilizzare questa classe con un ORM, probabilmente avremo bisogno di un costruttore predefinito.

Oltre a questi, vogliamo un costruttore per questa classe. Con Lombok possiamo avere tutto questo con alcune semplici annotazioni:

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

4. Definizione delle aspettative

Definiamo alcune aspettative per ciò che vogliamo ottenere sotto forma di unit test.

Il primo e fondamentale requisito è la presenza di valori di default dopo aver costruito un oggetto con un builder:

@Test public void givenBuilderWithDefaultValue_ThanDefaultValueIsPresent() { Pojo build = Pojo.builder() .build(); Assert.assertEquals("foo", build.getName()); Assert.assertTrue(build.isOriginal()); }

Ovviamente, questo test fallisce poiché l' annotazione @Builder non popola i valori. Lo sistemeremo presto.

Se usiamo un ORM, di solito si basa su un costruttore predefinito. Quindi, dovremmo aspettarci lo stesso comportamento dal costruttore predefinito come ci aspettiamo dal generatore:

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

In questa fase, questo test viene superato.

Adesso vediamo come possiamo far passare entrambi i test!

5. Lombok's Builder.Default Annotation

Da Lombok v1.16.16, possiamo usare l' annotazione interna di @Builder :

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

È semplice e leggibile, ma presenta alcuni difetti.

Con questo, i valori di default saranno presenti con il builder, facendo passare il primo caso di test. Sfortunatamente, il costruttore senza argomenti non otterrà i valori predefiniti, facendo fallire il secondo caso di test . Anche se il costruttore no-args non viene generato ma scritto in modo esplicito.

Questo effetto collaterale dell'annotazione Builder.Default è presente dall'inizio e probabilmente rimarrà con noi per molto tempo.

6. Inizializza il generatore

Possiamo provare a far passare entrambi i test definendo i valori predefiniti in un'implementazione di un builder minimalista:

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

In questo modo, entrambi i test passeranno.

Sfortunatamente, il prezzo è la duplicazione del codice. Per un POJO con decine di campi, potrebbe essere soggetto a errori mantenere la doppia inizializzazione.

Ma, se siamo disposti a pagare questo prezzo, dovremmo occuparci anche di un'altra cosa. Se rinominiamo la nostra classe utilizzando un refactoring all'interno del nostro IDE, la classe interna statica non verrà rinominata automaticamente. Quindi, Lombok non lo troverà e il nostro codice non funzionerà.

Per eliminare questo rischio, possiamo decorare l'annotazione del builder:

// 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. Utilizzo di toBuilder

@Builder supporta anche la generazione di un'istanza del builder da un'istanza della classe originale. Questa funzione non è abilitata per impostazione predefinita. Possiamo abilitarlo impostando il parametro toBuilder nell'annotazione del builder:

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

Con questo, possiamo sbarazzarci della doppia inizializzazione .

Ovviamente c'è un prezzo per questo. Dobbiamo istanziare la classe per creare un builder. Quindi, dobbiamo modificare anche i nostri test:

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

Anche in questo caso, entrambi i test vengono superati, quindi abbiamo lo stesso valore predefinito utilizzando il costruttore no-args come quando si utilizza il builder.

8. Conclusione

Quindi, abbiamo esaminato diverse opzioni per fornire valori predefiniti per il generatore Lombok.

L'effetto collaterale del Builder . Vale la pena tenere d'occhio l'annotazione predefinita . Ma anche le altre opzioni hanno i loro svantaggi. Quindi dobbiamo scegliere con attenzione in base alla situazione attuale.

Come sempre, il codice è disponibile su GitHub.