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.