Criando objetos de maneira elegante: Builder Pattern

Um dos Padrões de Projetos mais usados é o Builder Pattern. Como Joshua Bloch afirmou em seu excelente livro Effective Java, o uso desse padrão resulta em um API fluente com o código fácil de escrever e, mais importante ainda, fácil de ler, pois simula parâmetros opcionais nomeados como encontrados em Python e Scala.

Neste artigo, discutiremos o problema que ele resolve e como implementá-lo.

Começaremos criando um projeto Quarkus:

mvn io.quarkus:quarkus-maven-plugin:1.5.2.Final:create 
    -DprojectGroupId=br.com.helberbelmiro 
    -DprojectArtifactId=builder-pattern 
    -DclassName="br.com.helberbelmiro.builderpattern.PeopleApi" 
    -Dpath="/people" 
    -Dextensions="resteasy-jsonb"
    cd builder-pattern

Digamos que temos esta classe Person com atributos não nulos:

public class Person {
    private final String name;

    private final LocalDate birthDate;

    private final String email;

    private final String phoneNumber;

    private final String website;

    private final String facebookAccount;

    private final String instagramAccount;

    private final String twitterAccount;

    private final String githubAccount;   
    // Getters
}

Como você definiria o construtor dessa classe?

Visto que temos 9 atributos opcionais para inicializar, não há muitas boas alternativas que possamos usar. Eu criaria um único construtor que receberia os valores iniciais de todos os atributos:

public Person(String name, 
              LocalDate birthDate, 
              String email, 
              String phoneNumber, 
              String website, 
              String facebookAccount, 
              String instagramAccount, 
              String twitterAccount, 
              String githubAccount) {
    this.name = name;
    this.birthDate = birthDate;
    this.email = email;
    this.phoneNumber = phoneNumber;
    this.website = website;
    this.facebookAccount = facebookAccount;
    this.instagramAccount = instagramAccount;
    this.twitterAccount = twitterAccount;
    this.githubAccount = githubAccount;
}

Mas também não gosto dessa solução. A chamada do método ficaria bastante ilegível e teríamos que preencher alguns parâmetros com null.

Person p = new Person(
    "Steve Gates",
    LocalDate.of(1980, 12, 25),
    "stevegates@gmail.com",
    "1234598763",
    null,
    "sgates",
    "steve_gatez",
    null,
    "stevegates"
);

Sem olhar para a definição do construtor, você seria capaz de dizer qual conta do Instagram é usada acima ou quais são os atributos nulos da instância? Eu não seria.

O Builder Pattern resolve esse problema nos ajudando a tornar nosso código mais simples e legível.

Existem muitas variações de implementação, mas em teoria, todas elas fazem o mesmo. Vou te mostrar como eu normalmente faço.

Implementando o Builder Pattern

Vamos começar criando uma classe aninhada estática na classe Person chamada Builder com os mesmos atributos da classe Person e, para cada atributo, criaremos um método para definir os valores do construtor que retorna a instância do Builder. Eu normalmente nomeio os métodos com o prefixo “with” antes do nome do atributo, mas muitas vezes podemos ver pessoas usando “set” ou mesmo apenas o nome do atributo – withEmail(String), setEmail(String) ou email(String).

public class Person {

    (...)
    public static class Builder {
        private String name;
        private LocalDate birthDate;
        private String email;
        private String phoneNumber;
        private String website;
        private String facebookAccount;
        private String instagramAccount;
        private String twitterAccount;
        private String githubAccount;

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

        public Builder withBirthDate(LocalDate birthDate) {
            this.birthDate = birthDate;
            return this;
        }

        public Builder withEmail(String email) {
            this.email = email;
            return this;
        }

        public Builder withPhoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
            return this;
        }

        public Builder withWebsite(String website) {
            this.website = website;
            return this;
        }

        public Builder withFacebookAccount(String facebookAccount) {
            this.facebookAccount = facebookAccount;
            return this;
        }

        public Builder withInstagramAccount(String instagramAccount) {
            this.instagramAccount = instagramAccount;
            return this;
        }

        public Builder withTwitterAccount(String twitterAccount) {
            this.twitterAccount = twitterAccount;
            return this;
        }

        public Builder withGithubAccount(String githubAccount) {
            this.githubAccount = githubAccount;
            return this;
        }

    }

    (...)
}    

Agora, na classe Person, criaremos um método estático que irá criar uma instância da classe Builder:

public class Person {

    (...)

    public static Builder builder() {
        return new Builder();
    }
    
    (...)

}

Em seguida, vamos alterar o construtor da classe Person para:

private Person(Builder builder) {
    this.name = builder.name;
    this.birthDate = builder.birthDate;
    this.email = builder.email;
    this.phoneNumber = builder.phoneNumber;
    this.website = builder.website;
    this.facebookAccount = builder.facebookAccount;
    this.instagramAccount = builder.instagramAccount;
    this.twitterAccount = builder.twitterAccount;
    this.githubAccount = builder.githubAccount;
}

E finalmente criaremos o seguinte método na classe Builder:

public Person build() {
    return new Person(this);
}    

Agora temos nosso Builder pronto para usar. Vamos mudar nosso método na classe PeopleApi para retornar List<Person>:

@Path("/people")
@Produces(MediaType.APPLICATION_JSON)
public class PeopleApi {

    @GET
    public Response get() {
        final List  people = List.of(
            Person.builder()
                    .withName("Steve Gates")
                    .withBirthDate(LocalDate.of(1980, 12, 25))
                    .withEmail("stevegates@gmail.com")
                    .withPhoneNumber("1234598763")
                    .withFacebookAccount("sgates")
                    .withInstagramAccount("steve_gatez")
                    .withGithubAccount("stevegates")
                    .build()
        );

        return Response.ok(people).build();
    }

}    

Observe que agora podemos identificar facilmente qual valor será definido em cada atributo. Além disso, não precisamos definir os nulos.

Então, vamos testar nossa aplicação executando o seguinte comando:

./mvnw compile quarkus:dev

Após a construção, a saída deve ser semelhante a esta:

Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______
--/ __ / / / / _ | / _ / //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ / 
--________/_/ |_/_/|_/_/|_|____/___/
2020-06-21 17:37:19,439 INFO  [io.quarkus] (Quarkus Main Thread) builder-pattern 1.0-SNAPSHOT on JVM (powered by Quarkus 1.5.2.Final) started in 1.208s. Listening on: http://0.0.0.0:8080
2020-06-21 17:37:19,460 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2020-06-21 17:37:19,461 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, resteasy-jsonb]    

Sua aplicação está ativa e aguardando por requisições. Então, vamos fazer uma requisição GET:

curl http://localhost:8080/people    

Devemos receber a lista que criamos na classe PeopleApi:

[{"birthDate":"1980-12-25","email":"stevegates@gmail.com","facebookAccount":"sgates","githubAccount":"stevegates","instagramAccount":"steve_gatez","name":"Steve Gates","phoneNumber":"1234598763"}]

Funciona!

É isso aí. Usando o Builder Pattern, conseguimos criar um objeto de uma forma elegante, fluente e legível.

Se você quiser ler mais sobre esse e outros Design Patterns, recomendo o livro Head First Design Patterns: A Brain-Friendly Guide e o clássico e essencial Effective Java.

Você pode encontrar esse código-fonte no GitHub.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *