Geração automática do padrão Builder com FreeBuilder

Geração automática do padrão Builder com FreeBuilder

1. Visão geral

Neste tutorial, usaremosFreeBuilder library para gerar classes construtoras em Java.

2. Padrão de Design do Construtor

Builder é um dosCreation Design Patterns mais amplamente usados ​​em linguagens orientadas a objetos. Éabstracts the instantiation of a complex domain object and provides a fluent API para criar uma instância. Dessa forma, ajuda a manter uma camada de domínio concisa.

Apesar de sua utilidade, um construtor geralmente é complexo de implementar, principalmente em Java. Objetos de valor ainda mais simples exigem muito código padrão.

3. Implementação do construtor em Java

Antes de prosseguirmos com o FreeBuilder, vamos implementar um construtor padrão para nossoEmployee class:

public class Employee {

    private final String name;
    private final int age;
    private final String department;

    private Employee(String name, int age, String department) {
        this.name = name;
        this.age = age;
        this.department = department;
    }
}

E um sclassBuilder interno:

public static class Builder {

    private String name;
    private int age;
    private String department;

    public Builder setName(String name) {
        this.name = name;
        return this;
    }

    public Builder setAge(int age) {
        this.age = age;
        return this;
    }

    public Builder setDepartment(String department) {
        this.department = department;
        return this;
    }

    public Employee build() {
        return new Employee(name, age, department);
    }
}

Assim, podemos agora usar o construtor para instanciar o sobjetoEmployee :

Employee.Builder emplBuilder = new Employee.Builder();

Employee employee = emplBuilder
  .setName("example")
  .setAge(12)
  .setDepartment("Builder Pattern")
  .build();

Como mostrado acima,a lot of boilerplate code is necessary for implementing a builder class.

Nas seções posteriores, veremos como o FreeBuilder pode simplificar instantaneamente essa implementação.

4. Dependência do Maven

Para adicionar a biblioteca FreeBuilder, adicionaremosFreeBuilder Maven dependency em nossopom.xml:


    org.inferred
    freebuilder
    2.4.1

5. FreeBuilder Anotação

5.1. Gerando um construtor

O FreeBuilder é uma biblioteca de código aberto que ajuda os desenvolvedores a evitar o código padrão durante a implementação de classes do construtor. Ele utiliza o processamento de anotação em Java para gerar uma implementação concreta do padrão do construtor.

Vamosannotate our Employee class from the earlier section with @*FreeBuilder* ver como ele gera automaticamente a classe builder:

@FreeBuilder
public interface Employee {

    String name();
    int age();
    String department();

    class Builder extends Employee_Builder {
    }
}

É importante destacar queEmployee is now an *interface* r ao invés de uma classe POJO. Além disso, ele contém todos os atributos de umEmployee objeto como métodos.

Antes de continuarmos a usar esse construtor, devemos configurar nossos IDEs para evitar problemas de compilação. ComoFreeBuilder gera automaticamente o sclassEmployee_Builder durante a compilação, o IDEusually complains of ClassNotFoundException on line number 8.

Para evitar esses problemas,we need to enable annotation processing in IntelliJ or Eclipse. E ao fazer isso, usaremos o processador de anotação do FreeBuilderorg.inferred.freebuilder.processor.Processor.. Além disso, o diretório usado para gerar esses arquivos de origem deve ser marcado comoGenerated Sources Root.

Como alternativa,we can also execute mvn install para construir o projeto e gerar as classes de construtor necessárias.

Finalmente, compilamos nosso projeto e agora podemos usar o sclassEmployee.Builder :

Employee.Builder builder = new Employee.Builder();

Employee employee = builder.name("example")
  .age(10)
  .department("Builder Pattern")
  .build();

Em suma, existem duas diferenças principais entre essa e a classe do construtor que vimos anteriormente. Primeiro,we must set the value for all attributes of the Employee class. Otherwise, it throws an*IllegalStateException*.

Veremos como o FreeBuilder lida com atributos opcionais em uma seção posterior.

Em segundo lugar, os nomes dos métodos deEmployee.Builder  não seguem as convenções de nomenclatura JavaBean. Veremos isso na próxima seção.

5.2. Convenção de Nomenclatura JavaBean

Para forçar o FreeBuilder a seguir a convenção de nomenclatura JavaBean, devemosrename our methods in Employee and prefix the methods with*get*:

@FreeBuilder
public interface Employee {

    String getName();
    int getAge();
    String getDepartment();

    class Builder extends Employee_Builder {
    }
}

Isso irá gerar getters e setters que seguem a convenção de nomenclatura JavaBean:

Employee employee = builder
  .setName("example")
  .setAge(10)
  .setDepartment("Builder Pattern")
  .build();

5.3. Métodos do mapeador

Juntamente com getters e setters, o FreeBuilder também adiciona métodos de mapeamento na classe builder. Esses métodos mapeadoresaccept a UnaryOperator as input, permitem que os desenvolvedores calculem valores de campos complexos.

Suponha que nossa classeEmployee também tenha um campo de salário:

@FreeBuilder
public interface Employee {
    Optional getSalaryInUSD();
}

Agora, suponha que precisamos converter a moeda do salário fornecido como entrada:

long salaryInEuros = INPUT_SALARY_EUROS;
Employee.Builder builder = new Employee.Builder();

Employee employee = builder
  .setName("example")
  .setAge(10)
  .mapSalaryInUSD(sal -> salaryInEuros * EUROS_TO_USD_RATIO)
  .build();

O FreeBuilder fornece esses métodos de mapeamento para todos os campos.

6. Valores padrão e verificações de restrição

6.1. Definindo valores padrão

A implementação deEmployee.Builder que discutimos até agora espera que o cliente passe valores para todos os campos. Na verdade, ele falha no processo de inicialização com um caso deIllegalStateException en de campos ausentes.

Para evitar tais falhas,we can either set default values for fields or make them optional.

Podemos definir valores padrão no construtorEmployee.Builder :

@FreeBuilder
public interface Employee {

    // getter methods

    class Builder extends Employee_Builder {

        public Builder() {
            setDepartment("Builder Pattern");
        }
    }
}

Portanto, simplesmente definimos odepartment padrão do construtor. Este valor será aplicado a todos osEmployee objetos.

6.2. Verificações de restrição

Geralmente, temos certas restrições nos valores dos campos. Por exemplo, um e-mail válido deve conter um “@” ou a idade de umEmployee deve estar dentro de um intervalo.

Tais restrições exigem que coloquemos validações nos valores de entrada. EFreeBuilder allows us to add these validations by merely overriding the setter methods:

@FreeBuilder
public interface Employee {

    // getter methods

    class Builder extends Employee_Builder {

        @Override
        public Builder setEmail(String email) {
            if (checkValidEmail(email))
                return super.setEmail(email);
            else
                throw new IllegalArgumentException("Invalid email");

        }

        private boolean checkValidEmail(String email) {
            return email.contains("@");
        }
    }
}

7. Valores opcionais

7.1. Usando camposOptional

Alguns objetos contêm campos opcionais, cujos valores podem ser vazios ou nulos. FreeBuilder allows us to define such fields using the Java Optional type:

@FreeBuilder
public interface Employee {

    String getName();
    int getAge();

    // other getters

    Optional getPermanent();

    Optional getDateOfJoining();

    class Builder extends Employee_Builder {
    }
}

Agora podemos pular o fornecimento de qualquer valor paraOptional fields:

Employee employee = builder.setName("example")
  .setAge(10)
  .setPermanent(true)
  .build();

Notavelmente, simplesmente passamos o valor para o campopermanent em vez de umOptional.  Desde que não definimosthe value for dateOfJoining field, it will be Optional.empty() which is the default for Optional fields.

7.2. Usando campos@Nullable

Embora o uso deOptional seja recomendado para lidar comnulls em Java, o FreeBuilder permiteus to use @Nullable for backward compatibility:

@FreeBuilder
public interface Employee {

    String getName();
    int getAge();

    // other getter methods

    Optional getPermanent();
    Optional getDateOfJoining();

    @Nullable String getCurrentProject();

    class Builder extends Employee_Builder {
    }
}

O uso deOptionalis ill-advised in some cases, que é outra razão pela qual@Nullable  é preferido para classes builder.

8. Coleções e mapas

O FreeBuilder possui suporte especial para coleções e mapas:

@FreeBuilder
public interface Employee {

    String getName();
    int getAge();

    // other getter methods

    List getAccessTokens();
    Map getAssetsSerialIdMapping();


    class Builder extends Employee_Builder {
    }
}

FreeBuilder adicionaconvenience methods to add input elements into the Collection in the builder class:

Employee employee = builder.setName("example")
  .setAge(10)
  .addAccessTokens(1221819L)
  .addAccessTokens(1223441L, 134567L)
  .build();

Também existe um métodogetAccessTokens() na classe do construtor quereturns an unmodifiable list. Da mesma forma, paraMap:

Employee employee = builder.setName("example")
  .setAge(10)
  .addAccessTokens(1221819L)
  .addAccessTokens(1223441L, 134567L)
  .putAssetsSerialIdMapping("Laptop", 12345L)
  .build();

O métodogetter paraMap alsoreturns an unmodifiable map para o código do cliente.

9. Construtores aninhados

Para aplicativos do mundo real, podemos ter denest a lot of value objects for our domain entities. E como os objetos aninhados podem precisar de implementações de construtor, o FreeBuilder permite tipos construtíveis aninhados.

Por exemplo, suponha que temos um tipo complexo aninhadoAddress em a classeEmployee :

@FreeBuilder
public interface Address {

    String getCity();

    class Builder extends Address_Builder {
    }
}

Agora, o FreeBuilder gerasetter methods que levamAddress.Builder como uma entrada junto com o tipoAddress :

Address.Builder addressBuilder = new Address.Builder();
addressBuilder.setCity(CITY_NAME);

Employee employee = builder.setName("example")
  .setAddress(addressBuilder)
  .build();

Notavelmente, o FreeBuilder também adiciona um método acustomize the existing Address object in the *Employee*:

Employee employee = builder.setName("example")
  .setAddress(addressBuilder)
  .mutateAddress(a -> a.setPinCode(112200))
  .build();

Junto comFreeBuilder types, o FreeBuilder também permite o aninhamento de outros construtores, comoprotos.

10. Construindo objeto parcial

Como discutimos antes, o FreeBuilder lança umIllegalStateException  para qualquer violação de restrição - por exemplo, valores ausentes para campos obrigatórios.

Embora sejadesired for production environments, complicaunit testing that is independent of constraints in general.

Para relaxar essas restrições, o FreeBuilder nos permite criar objetos parciais:

Employee employee = builder.setName("example")
  .setAge(10)
  .setEmail("[email protected]")
  .buildPartial();

assertNotNull(employee.getEmail());

Portanto, embora não tenhamos definido todos os campos obrigatórios para umEmployee, ainda podemos verificar se o campoemail  tem um valor válido.

11. MétodotoString()  personalizado

Com objetos de valor,we often need to add a custom toString() implementation. FreeBuilder permite isso por meio deabstract classes:

@FreeBuilder
public abstract class Employee {

    abstract String getName();

    abstract int getAge();

    @Override
    public String toString() {
        return getName() + " (" + getAge() + " years old)";
    }

    public static class Builder extends Employee_Builder{
    }
}

DeclaramosEmployee  como uma classe abstrata em vez de uma interface e fornecemos uma simplificaçãotoString() personalizada.

12. Comparação com outras bibliotecas do construtor

A implementação do construtor que discutimos neste artigo é muito semelhante àquelas deLombok,Immutables ou qualquer outroannotation processor. No entanto,there are a few distinguishing characteristics tque já discutimos:

    • Métodos do mapeador

    • Tipos de construção aninhados

    • Objetos Parciais

13. Conclusão

Neste artigo, usamos a biblioteca FreeBuilder para gerar uma classe de construtor em Java. Implementamos várias customizações de uma classe builder com a ajuda de anotações,thus reducing the boilerplate code required for its implementation.

Também vimos como o FreeBuilder é diferente de algumas das outras bibliotecas e discutimos brevemente algumas dessas características neste artigo.

Todos os exemplos de código estão disponíveis emGitHub.