Nesta segunda parte do artigo sobre o design pattern Builder, vou mostrar o exemplo dado na primeira parte (<<leia aqui>>), utilizando o conceito de interface fluente. Também falarei sobre a aplicação de builders para criação de dados de testes (Test Data Builders).
EXEMPLO – INTERFACE FLUENTE
Começamos alterando a interface do builder abstrato “CriadorDeGuerreiro”, de forma que os métodos que constroem partes do objeto-produto agora retornem o próprio builder:
public abstract class CriadorDeGuerreiro
{
protected Guerreiro _guerreiro;
public Guerreiro ObterGuerreiro()
{
return _guerreiro;
}
// retorno alterado de 'void' para 'CriadorDeGuerreiro'
public abstract CriadorDeGuerreiro ComEspada();
public abstract CriadorDeGuerreiro ComArmadura();
public abstract CriadorDeGuerreiro ComArco();
}
Consequentemente, devemos alterar nossos builders concretos para atender a interface acima. Mostrarei como fica apenas com um deles:
public class CriadorDeGuerreiroMedieval : CriadorDeGuerreiro
{
public CriadorDeGuerreiroMedieval()
{
_guerreiro = new GuerreiroMedieval();
}
public override CriadorDeGuerreiro ComEspada()
{
_guerreiro.EscolherEspada("espada medieval");
return this;
}
public override CriadorDeGuerreiro ComArmadura()
{
_guerreiro.ColocarArmadura("armadura medieval");
return this;
}
public override CriadorDeGuerreiro ComArco()
{
_guerreiro.EscolherArco("arco medieval");
return this;
}
}
Percebam que agora os métodos que montam parte do produto retornam o próprio builder (return this). Se vocês não conhecem o conceito de interface fluente, verão o porquê disso mais abaixo.
A última modificação a ser feita agora é no director “Exercito” para construir o objeto-produto de forma fluente:
public class Exercito
{
public void ConstruirGuerreiro(CriadorDeGuerreiro criadorDeGuerreiro)
{
// montagem do produto de forma fluente
criadorDeGuerreiro.ComArco().ComArmadura().ComEspada();
}
}
Notem que agora, como cada método retorna o próprio builder (o próprio objeto corrente), podemos chamá-los em cadeia (method chaining), na ordem que quisermos e quais deles quisermos, é claro. (Para exercitarmos esse código, podemos usar o código da aplicação console mostrado na parte 1 deste artigo, sem nenhuma modificação.)
TEST DATA BUILDERS
Uma boa aplicação para os builders é usá-los para criar objetos em cenários de testes (e é a forma como eu venho utilizando este padrão em meus últimos projetos).
A implementação do padrão pode ser mais simplificada em um projeto de testes. Como queremos apenas variar as partes criadas do objeto de acordo com o cenário de teste e não COMO cada parte é criada, não precisamos de um builder abstrato. Criamos apenas o builder concreto para o objeto e pronto!
Vejamos alguns exemplos:
[Test]
public void um_teste_qualquer()
{
var pedido = PedidoBuilder.UmPedido().SemNenhumItem().Build();
//......
}
[Test]
public void outro_teste()
{
//......
var pedido = PedidoBuilder.UmPedido()
.ComItem(produto1, 10)
.ComItem(produto2, 20)
.ComDescontoDe(0.5)
.Build();
//......
}
Podemos ver nos exemplos que ganhamos em legibilidade e em flexibilidade, pois podemos fazer a combinação de métodos que for mais adequada para cada teste. Também ganhamos em facilidade de manutenção, já que quando alteramos o objeto-produto (principalmente seu construtor), só precisaremos corrigir no builder, ao invés de sairmos fazendo o ajuste em dezenas de testes.
Além disso, PedidoBuilder é implementado de uma forma levemente diferente daquela mostrada nos exemplos do Guerreiro (considerações nos comentários):
public class PedidoBuilder
{
private double _desconto;
// PRIMEIRO: TORNAMOS O CONSTRUTOR PRIVADO
private PedidoBuilder() {}
// SEGUNDO: CRIAMOS UM FACTORY METHOD ESTÁTICO PARA DEIXAR MAIS EXPLÍCITO
// NO TESTE O QUE ESTÁ SENDO CRIADO
public static PedidoBuilder UmPedido()
{
return new PedidoBuilder();
}
// TERCEIRO: GUARDAMOS A PARTE CRIADA TEMPORARIAMENTE
public PedidoBuilder ComDescontoDe(double desconto)
{
_desconto = desconto;
return this;
}
// QUARTO: O PRODUTO SÓ É CRIADO NO FINAL, AO CHAMARMOS O MÉTODO QUE O RECUPERA
public Pedido Build()
{
var pedido = new Pedido(_desconto);
return pedido;
}
}
Simples, não é?
CONCLUSÃO
Nesta continuação do artigo sobre o design pattern Builder, vimos outras formas de implementação do mesmo, assim como uma forma de aplicá-lo em cenários de testes. Implementados de forma fluente, facilitam na implementação, legibilidade e manutenção dos testes.
———————————————————
Referência:
Freeman, Steve. Pryce, Nat. 2010. Growing Object-Oriented Software, Guided By Tests. Addison-Wesley.