Builders no Scala com named parameters

Durante a escrita dos testes das nossas aplicações, é muito comum instanciarmos objetos bem parecidos em diversos momentos. Por exemplo, quando precisamos de um usuario logado para efetuar as operações do nosso sistema, ou quando precisamos criar um novo usuário para testar algo relativo ao seu endereço. Em vários de nossos testes acabamos com um código que popula algum dos atributos de uma entidade:

User user = new User()
user.setLogin("algumLogin")
user.setSenha("algumaSenha")
user.setEmail("email@email.com.br")

Fazer uma vez ou outra não incomoda muito, mas quando esse código começa a se repetir, de vez em quando tendo que váriar os valores para criação do objeto, os testes podem ficar bastante sujos com a repetição dessas instruções.

Para minimizar o problema podemos utilizar os builders, classes cuja única responsabilidade é facilitar a criação de um objeto. A idéia é dar a possibilidade de escrever um código com interface mais fluente durante a escrita do teste. Por exemplo, para criar um usuário com nome, login e senha poderiamos ter o seguinte código:

new UserBuilder().comLogin(...).comSenha("").comEmail("")

Alguns vão considerar que esses simples uso de interface fluente podem ser considerados como umapequena DSL, um estilo de código que tenta se aproximar ao máximo de um contexto especifico. Um caso classico é a API de criteria do Hibernate. No atual projeto que participo na Caelum caímos no mesmo problema, mas como ele está sendo desenvolvido em Scala, usamos a mesma abordagem, aproveitando do poder da linguagem. A idéia é que para criar um novo usuario possamos escrever o seguinte código:

UserBuilder(login="login", senha="senha", email="algumemail@email.com.br")

Ou, caso apenas precisemos de um usuário com nome e email padrão, simplesmente não passamos nada. Por exemplo:

val u = UserBuilder()

A sacada é usar um pouco das features da linguagem para que possamos construir nossos builders sem muito esforço. A primeira é o sintax sugar que a linguagem oferece para não precisarmos do new no momento de instanciar um objeto.  Para isso basta criarmos um object com um método apply:

object UserBuilder {
  def apply(login:String, password:String, email:String) = 
        new User(login, password, email)  
}

Toda classe que é definida sendo um object serve como uma classe de métodos utilitários, muito parecido com os helpers que vemos por aí. O método apply é o que faz a jogada de não precisarmos do new. Aqui já ganhamos uma facilidade da linguagem, ele nos permite que referenciemos os nomes dos parâmetros no momento da passagem deles. A criação de um usuário com nome e email ficaria assim:

UserBuilder(login="login", senha="senha", email="algumemail@email.com.br")

Para finalizar, queremos deixar valores default nos parâmetros, para quando não for necessária nenhuma customização. Basta atribuir alguns valores na definição dos parâmetros:

object UserBuilder {
  def apply(login:String = "Fulano", password:String = "pass", email:String = "fulano@provedor.com") = 
        new User(login, password, email)  
}

Além de suportar maneiras diferentes de programar, por exemplo, dando a possibilidade de utilização do paradigma funcional, Scala também oferece muita melhoria na estrutura da linguagem, o que faz com que ela venha sendo cada vez mais comentada pela comunidade. No caso do uso para testes, poderíamos colocar diversas chamadas a esses builders dentro de um ObjectMother.

Indo mais além, usamos aqui na Caelum nossa DSL em cima da JPA-Hibernate, para facilitar nossas consultas ao banco de dados, possibilitando escrever:

val list = session.from[User].where("age" <= 25).asList[User]

8 Comentários

  1. galmeida 19/05/2011 at 17:43 #

    confesso que tive dificuldade p/ ler o título do artigo, bati o olho e achei que o titulo era em inglês, só depois que percebi que estava português (ou quase, afinal em português só as preposições)

  2. Arthur Moura Carvalho 19/05/2011 at 18:21 #

    Há pouco fiz um post (uma dúvida) no Tectura sobre Builders também.
    A dúvida já foi esclarecida e talvez possa trazer mais informações sobre formas de usar o Builder com adição da Herança.

    http://www.tectura.com.br/topics/criando_objetos_com_padrao_builder

  3. Sérgio Lopes 19/05/2011 at 18:30 #

    Seria possível usar os named params direto no construtor e não usar uma segunda classe para isso?

  4. Adriano Almeida 20/05/2011 at 04:31 #

    Oi Sergio, é possível usar os nameds no construtor sim. Eu particularmente não gosto muito de métodos apply em companion objects só para evitar de se dar o new. Cria uma complexidade desnecessária ao meu ponto de vista com pouquíssimo ganho, ou quase nada, que é evitar de se usar o new.

  5. Alberto Souza 20/05/2011 at 10:23 #

    Oi Sergio, como Adriano falou daria para fazer sem problemas. O suporte a named parameters é feature padrão da versão 2.8. O detalhe no contexto do post, é que os builders estão sendo utilizados para facilitar os testes, por isso os valores default. Se tivéssemos deixados esses valores direto na classe User, estaríamos dando a possibilidade de alguém instanciar esse cara sem passar os parâmetros necessários.
    Em relação ao uso do apply a idéia é diminuir ruído de código em situações como a que está abaixo por exemplo:
    ex1) new List(new User(…).new User)
    Com o apply ficamos assim:
    List(User(…),User(…)) . Apenas ruído mesmo, nada mais.

  6. Douglas Rodrigo 24/05/2011 at 17:39 #

    Olá pessoal, nós desenvolvemos um framework chamado fixture-factory (https://github.com/aparra/fixture-factory) feito em java, apesar do post ser relacionado aos named parameters do Scala achei interessante mencionar como uma alternativa p/ criar os objetos mocks p/ testcases.
    Talvez quem sabe uma DSL em Scala p/ o framework :)
    Exemplo da api em java:

    Fixture.of(Address.class).addTemplate(“valid”, new Rule(){{
    add(“id”, random(Long.class, range(1L, 100L)));
    add(“street”, random(“Paulista Avenue”, “Ibirapuera Avenue”));
    add(“city”, “São Paulo”);
    add(“state”, “${city}”);
    add(“country”, “Brazil”);
    add(“zipCode”, random(“06608000″, “17720000″));
    }});

  7. Léo Neves 24/05/2011 at 19:49 #

    Alberto, assisti uma palestra sua no JustJava. Eu estou começando a aprender Scala mas tenho um problema. Estou justamente fazendo um projeto scala com VRaptor, ta ficando bem legal. Mas tanto o plugin do netBeans quanto do eclipse, vivem dando problema, às vezes não reconhecem código scala, estou querendo sair das IDEs. Lembro que na sua palestra você falou, e usou uma IDE ( ou só editor com builder) bacana. Qual seria esse programa? Estou procurando mas não acho.
    Valeu! Ah, pelo post também.

  8. Alberto Souza 24/05/2011 at 20:01 #

    oi leo, eu. usei o intellij. comunity edition.

Deixe uma resposta