Automatische generatie van het Builder-patroon met FreeBuilder

1. Overzicht

In deze tutorial gebruiken we de FreeBuilder-bibliotheek om builder-klassen in Java te genereren.

2. Bouwer ontwerppatroon

Builder is een van de meest gebruikte Creation Design Patterns in objectgeoriënteerde talen. Het abstraheert de instantiatie van een complex domeinobject en biedt een vloeiende API voor het maken van een instantie. Het helpt daarbij om een ​​beknopte domeinlaag te behouden.

Ondanks zijn bruikbaarheid is een builder over het algemeen complex om te implementeren, vooral in Java. Zelfs eenvoudiger waardeobjecten vereisen veel boilerplate-code.

3. Builder-implementatie in Java

Voordat we verder gaan met FreeBuilder, moeten we een boilerplate-builder implementeren voor onze Werknemer klasse:

openbare klasse Medewerker {privé finale String naam; privé laatste int leeftijd; privé-finale String-afdeling; private medewerker (String naam, int leeftijd, String afdeling) {this.name = naam; this.age = leeftijd; this.department = afdeling; }}

En een innerlijk Bouwer klasse:

openbare statische klasse Builder {private String-naam; privé int leeftijd; particuliere String-afdeling; public Builder setName (String naam) {this.name = naam; dit teruggeven; } public Builder setAge (int leeftijd) {this.age = age; dit teruggeven; } public Builder setDepartment (String department) {this.department = department; dit teruggeven; } public Employee build () {retourneer nieuwe medewerker (naam, leeftijd, afdeling); }}

Dienovereenkomstig kunnen we nu de builder gebruiken om het Werknemer voorwerp:

Employee.Builder emplBuilder = nieuwe Employee.Builder (); Werknemer werknemer = emplBuilder .setName ("baeldung") .setAge (12) .setDepartment ("Bouwerpatroon") .build ();

Zoals hierboven getoond, er is veel boilerplate-code nodig voor het implementeren van een builder-klasse.

In de latere secties zullen we zien hoe FreeBuilder deze implementatie onmiddellijk kan vereenvoudigen.

4. Maven Afhankelijkheid

Om de FreeBuilder-bibliotheek toe te voegen, voegen we de FreeBuilder Maven-afhankelijkheid toe aan onze pom.xml:

 org. afgeleide freebuilder 2.4.1 

5. FreeBuilder Annotatie

5.1. Een bouwer genereren

FreeBuilder is een open-sourcebibliotheek die ontwikkelaars helpt de standaardcode te vermijden tijdens het implementeren van builderklassen. Het maakt gebruik van annotatieverwerking in Java om een ​​concrete implementatie van het builderpatroon te genereren.

Goed annoteer onze Werknemer klasse uit de eerdere sectie met @FreeBuilderen zie hoe het automatisch de builder-klasse genereert:

@FreeBuilder openbare interface Werknemer {Stringnaam (); int leeftijd (); String afdeling (); class Builder breidt Employee_Builder {}} uit

Het is belangrijk erop te wijzen Werknemer is nu een koppelin plaats van een POJO-les. Bovendien bevat het alle attributen van een Werknemer object als methoden.

Voordat we deze builder blijven gebruiken, moeten we onze IDE's configureren om compilatieproblemen te voorkomen. Sinds FreeBuilder genereert automatisch het Employee_Builder class tijdens de compilatie, de IDE klaagt meestal over ClassNotFoundException op regel nummer 8.

Om dergelijke problemen te voorkomen, we moeten annotatieverwerking inschakelen in IntelliJ of Eclipse. En terwijl we dit doen, gebruiken we de annotatieprocessor van FreeBuilder org.inferred.freebuilder.processor.Processor. Bovendien moet de map die wordt gebruikt voor het genereren van deze bronbestanden worden gemarkeerd als Generated Sources Root.

Alternatief, we kunnen ook uitvoeren mvn installeren om het project te bouwen en de vereiste bouwklassen te genereren.

Eindelijk hebben we ons project gecompileerd en kunnen we nu de Medewerker Bouwer klasse:

Employee.Builder builder = nieuwe Employee.Builder (); Werknemer werknemer = builder.name ("baeldung") .age (10) .department ("Builder Pattern") .build ();

Al met al zijn er twee belangrijke verschillen tussen deze klasse en de bouwersklasse die we eerder zagen. Eerste, we moeten de waarde instellen voor alle attributen van de Werknemer klasse. Anders gooit het een IllegalStateException.

We zullen zien hoe FreeBuilder omgaat met optionele attributen in een later gedeelte.

Ten tweede, de methodenamen van Medewerker Bouwer volg de naamgevingsconventies van JavaBean niet. We zullen dit in de volgende sectie zien.

5.2. JavaBean-naamgevingsconventie

Om ervoor te zorgen dat FreeBuilder de JavaBean-naamgevingsconventie volgt, moeten we hernoem onze methoden in Werknemer en voeg een voorvoegsel van de methoden toe krijgen:

@FreeBuilder openbare interface Werknemer {String getName (); int getAge (); String getDepartment (); class Builder breidt Employee_Builder {}} uit

Dit genereert getters en setters die de JavaBean-naamgevingsconventie volgen:

Werknemer werknemer = bouwer .setName ("baeldung") .setAge (10) .setDepartment ("Bouwerpatroon") .build ();

5.3. Mapper-methoden

In combinatie met getters en setters voegt FreeBuilder ook mapper-methoden toe in de builder-klasse. Deze mapper-methoden accepteer een UnaryOperator als invoer, waardoor ontwikkelaars complexe veldwaarden kunnen berekenen.

Stel dat onze Werknemer klasse heeft ook een salarisveld:

@FreeBuilder openbare interface Werknemer {Optioneel getSalaryInUSD (); }

Stel nu dat we de valuta van het salaris dat als invoer is opgegeven, moeten omrekenen:

lang salarisInEuros = INPUT_SALARY_EUROS; Employee.Builder builder = nieuwe Employee.Builder (); Werknemer werknemer = bouwer .setName ("baeldung") .setAge (10) .mapSalaryInUSD (sal -> salarisInEuros * EUROS_TO_USD_RATIO) .build ();

FreeBuilder biedt dergelijke mapper-methoden voor alle velden.

6. Standaardwaarden en beperkingscontroles

6.1. Standaardwaarden instellen

De Medewerker Bouwer implementatie die we tot nu toe hebben besproken, verwacht dat de klant waarden doorgeeft voor alle velden. In feite mislukt het initialisatieproces met een IllegalStateException in het geval van ontbrekende velden.

Om dergelijke storingen te voorkomen, we kunnen ofwel standaardwaarden voor velden instellen of ze optioneel maken.

We kunnen standaardwaarden instellen in de Medewerker Bouwer constructeur:

@FreeBuilder openbare interface Werknemer {// getter methoden klasse Builder breidt Employee_Builder {openbare Builder () {setDepartment ("Builder Pattern") uit; }}}

Dus we stellen gewoon de standaard in afdeling in de constructor. Deze waarde is van toepassing op iedereen Werknemer voorwerpen.

6.2. Beperkingscontroles

Meestal hebben we bepaalde beperkingen voor veldwaarden. Een geldig e-mailadres moet bijvoorbeeld een "@" of de leeftijd van een bevatten Werknemer moet binnen een bereik zijn.

Dergelijke beperkingen vereisen dat we validaties op invoerwaarden plaatsen. En Met FreeBuilder kunnen we deze validaties toevoegen door alleen de setter methoden:

@FreeBuilder openbare interface Werknemer {// getter methoden klasse Builder breidt Employee_Builder uit {@Override openbare Builder setEmail (String e-mail) {if (checkValidEmail (e-mail)) return super.setEmail (e-mail); anders werpen nieuwe IllegalArgumentException ("Ongeldige e-mail"); } private boolean checkValidEmail (String e-mail) {return email.contains ("@"); }}}

7. Optionele waarden

7.1. Gebruik makend van Optioneel Velden

Sommige objecten bevatten optionele velden, waarvan de waarden leeg of null kunnen zijn. Met FreeBuilder kunnen we dergelijke velden definiëren met behulp van Java Optioneel type:

@FreeBuilder openbare interface Werknemer {String getName (); int getAge (); // andere getters Optioneel getPermanent (); Optioneel getDateOfJoining (); class Builder breidt Employee_Builder {}} uit

Nu kunnen we het verstrekken van enige waarde voor overslaan Optioneel velden:

Werknemer werknemer = builder.setName ("baeldung") .setAge (10) .setPermanent (true) .build ();

We hebben met name de waarde gewoon doorgegeven aan permanent veld in plaats van een Optioneel. Omdat we niet gingen zitten de waarde voor datum van toetreding veld, zal het zijn Optioneel.empty () wat de standaard is voor Optioneel velden.

7.2. Gebruik makend van @Nullable Velden

Hoewel het gebruik van Optioneel wordt aanbevolen voor gebruik nuls in Java, staat FreeBuilder toe ons te gebruiken @Nullable voor achterwaartse compatibiliteit:

@FreeBuilder openbare interface Werknemer {String getName (); int getAge (); // andere getter-methoden Optioneel getPermanent (); Optioneel getDateOfJoining (); @Nullable String getCurrentProject (); class Builder breidt Employee_Builder {}} uit

Het gebruik van Optioneel is in sommige gevallen onverstandig, wat een andere reden is @Nullable heeft de voorkeur voor bouwklassen.

8. Collecties en kaarten

FreeBuilder heeft speciale ondersteuning voor verzamelingen en kaarten:

@FreeBuilder openbare interface Werknemer {String getName (); int getAge (); // andere getter-methoden Lijst getAccessTokens (); Kaart getAssetsSerialIdMapping (); class Builder breidt Employee_Builder {}} uit

FreeBuilder voegt toe gemaksmethoden om invoerelementen toe te voegen aan de verzameling in de builder-klasse:

Werknemer werknemer = builder.setName ("baeldung") .setAge (10) .addAccessTokens (1221819L) .addAccessTokens (1223441L, 134567L) .build ();

Er is ook een getAccessTokens () methode in de builder-klasse die geeft een niet-wijzigbare lijst terug. Evenzo voor Kaart:

Werknemer werknemer = builder.setName ("baeldung") .setAge (10) .addAccessTokens (1221819L) .addAccessTokens (1223441L, 134567L) .putAssetsSerialIdMapping ("Laptop", 12345L) .build ();

De getter methode voor Kaart ook geeft een niet-wijzigbare kaart terug naar de klantcode.

9. Geneste bouwers

Voor toepassingen in de echte wereld is het mogelijk dat we dat moeten doen nest veel waardeobjecten voor onze domeinentiteiten. En aangezien de geneste objecten zelf builder-implementaties nodig kunnen hebben, staat FreeBuilder geneste build-typen toe.

Stel dat we een genest complex type hebben Adres in de Werknemer klasse:

@FreeBuilder openbare interface Adres {String getCity (); class Builder breidt Address_Builder {}} uit

Nu genereert FreeBuilder setter methoden die nemen Address.Builder als input samen met Adres type:

Address.Builder addressBuilder = nieuw Address.Builder (); addressBuilder.setCity (CITY_NAME); Werknemer werknemer = builder.setName ("baeldung") .setAddress (addressBuilder) .build ();

Met name voegt FreeBuilder ook een methode toe aan pas het bestaande aan Adres object in het Werknemer:

Werknemer werknemer = builder.setName ("baeldung") .setAddress (addressBuilder) .mutateAddress (a -> a.setPinCode (112200)) .build ();

Samen met FreeBuilder typen, laat FreeBuilder ook het nesten van andere bouwers toe, zoals protos.

10. Gebouw gedeeltelijk object

Zoals we eerder hebben besproken, gooit FreeBuilder een IllegalStateException voor elke schending van een beperking - bijvoorbeeld ontbrekende waarden voor verplichte velden.

Hoewel dit is gewenst voor productieomgevingen, het bemoeilijkt unit testing dat onafhankelijk is van algemene beperkingen.

Om dergelijke beperkingen te verminderen, stelt FreeBuilder ons in staat om gedeeltelijke objecten te bouwen:

Werknemer werknemer = builder.setName ("baeldung") .setAge (10) .setEmail ("[email protected]") .buildPartial (); assertNotNull (employee.getEmail ());

Dus ook al hebben we niet alle verplichte velden voor een Werknemer, konden we nog steeds verifiëren dat de e-mail veld heeft een geldige waarde.

11. Aangepast toString () Methode

Met waardeobjecten, we moeten vaak een gewoonte toevoegen toString () implementatie. FreeBuilder laat dit toe abstract klassen:

@FreeBuilder openbare abstracte klasse Werknemer {abstracte String getName (); abstract int getAge (); @Override public String toString () {return getName () + "(" + getAge () + "jaar oud)"; } public static class Builder breidt Employee_Builder uit {}}

We verklaarden Werknemer als een abstracte klasse in plaats van een interface en voorzien van een aangepaste toString () implementatie.

12. Vergelijking met andere Builder-bibliotheken

De bouwerimplementatie die we in dit artikel hebben besproken, lijkt sterk op die van Lombok, Immutables of een andere annotatieprocessor. Echter, er zijn een paar onderscheidende kenmerken die we al hebben besproken:

    • Mapper-methoden
    • Geneste bouwbare typen
    • Gedeeltelijke objecten

13. Conclusie

In dit artikel hebben we de FreeBuilder-bibliotheek gebruikt om een ​​builder-klasse in Java te genereren. We hebben verschillende aanpassingen van een builder-klasse geïmplementeerd met behulp van annotaties, waardoor de standaardcode die nodig is voor de implementatie ervan, wordt verminderd.

We hebben ook gezien hoe FreeBuilder verschilt van sommige van de andere bibliotheken en hebben enkele van die kenmerken in dit artikel kort besproken.

Alle codevoorbeelden zijn beschikbaar op GitHub.