Builder

Objetivo

Prover uma forma de facilitar a montagem de objetos que de outra forma seria complexa, propensa de erros ou verbosa.

Propósito

Objetos são compostos por outros objetos e todos os objetos precisam ser criados e configurados de alguma forma antes de serem realmente úteis. Contudo, nem sempre a construção de um objetos é simples o suficiente para ser escrita em duas linhas de código. Por outro lado talvez existam regras que não permitem criar o objeto de forma qualquer.

Ao codificar orientado a objetos é comum representarmos entidades do sistema como estruturas de objetos aninhados ou utilizar objetos no padrão Property Bag com várias propriedades. A construção de objetos deste tipo pode ser complexa e obrigar a escrever muito código burocrático devido à estrutura interna do objeto estar encapsulada.

Construir um objeto complexo é uma tarefa burocrática, verbosa, propensa a erros e pouco controlável.

A solução clássica, mas que realmente funciona bastante bem, é criar um outro objeto cuja responsabilidade é a correta montagem do objeto que queremos construir. Para que não haja confusão com o conceito de construtor, passarei a referir à responsabilidade do objeto que implementa o padrão Builder como "montar". Um construtor, constrói, um Builder, monta.

O objeto Builder é responsável por montar um outro objeto. Esse outro objeto que é montado é chamado produto, no contexto deste padrão. O objeto Builder conta com vários métodos na sua interface de forma a poder ser informado das características que o produto terá e um método que realmente irá montar o produto e entregá-lo. A chamada a este método termina o trabalho do objeto Builder.

Implementação

A estrutura clássica de uma implementação do objeto Builder tem este aspeto:

public class _Produto_Builder> {

	public void withPropriedadeA ( String propriedadeA ){...}
	public void withPropriedadeB ( Long propriedadeB ){...}
	...
	public _Produto_ build (){...}
}

Por exemplo, um objeto Builder para montar veículos seria assim:

public class VehicleBuilder {

	public void withColor ( Color color ){...}
	public void withEngime ( Engine engine ){...}
	...
	public Vehicle build (){...}
}

Note que as propriedades e os nomes dos métodos estão vinculados ao objeto a ser construído e fazem sentido para esse objeto e dentro do domínio em que ele se insere.

O último passo é deixar o Builder mais perto da linguagem do dominio do produto não apenas melhorando os nomes das propriedades, mas também o nome do método montador e da própria classe.

No caso, veiculos a montagem de veiculos tem um nome especifico: assemble. Então ficaria assim:

public class VehicleAssembler {

	public void withColor ( Color color ){...}
	public void withEngime ( Engine engine ){...}
	...
	public Vehicle assemble (){...}
}

Interface Fluente

Utilizar o objeto Builder resulta em invocar vários métodos no objeto e no fim invocar o método montador. Isto rapidamente se torna prolixo se não tivermos certos cuidados. Tomando o exemplo do montador de veículos ficaria mais ou menos assim:

VehicleAssembler builder = new VehicleAssembler();

builder.withColor ( Color.WHITE );
builder.withEngine ( new TurboEngineAs234 ());
builder.withDoorQuantity ( 5 );
builder.withFuelType ( FuelType.FLEX );

Vehicle vehicle = builder.assemble ();

Como se vê é muita coisa para escrever.Imagine que existem mais campos ou que o nome da variável builder é maior. Muita coisa para escrever. O ideal seria poder encadear as invocações com o mínimo possível de esforço, algo assim:

Vehicle vehicle = new VehicleAssembler ()
	.withColor ( Color.WHITE )
	.withEngine ( new TurboEngineAs234 ())
	.withDoorQuantity ( 5 )
	.withFuelType ( FuelType.FLEX )
	.assemble ();
Nada mau, mas poderíamos melhorar mais ainda. Porque o objeto  `Builder`  tem que montar o produto ele   conhece a sua estrutura e as opções dessa montagem, por isso é natural definir um `Builder` mais inteligente. O resultado é ainda menos coisas para escrever:
Vehicle vehicle = new VehicleAssembler ()
	.withWhiteColor()
	.withTurboEngineAs234()
	.withFiveDoors ()
	.withFuelFlex ()
	.assemble () ;

Esta forma de encadear os métodos de forma a que funcionem como uma macro instrução é chamado Method Channing [[2]] mas mais conhecido como Interface Fluente. Interface aqui se refere ao contrato do objeto Builder e Fluente porque diz muito com poucas palavras (pouca escrita).

**Interface Fluente**   é um nome bonito para o padrão   **Method Channing**   que é antigo mas pouco utilizado devido ao  vício de usar o padrão   [.pattern]*Property Bag*    para todos os objetos ignorando a real responsabilidade do objeto. Um *Builder* provido de uma interface fluente é também conhecido como *Fluent Builder*.

Implementar uma interface fluente é simples, basta que cada método sempre retorne o próprio objeto ( this ) permitindo o encadeamento das invocações de forma simples.

O uso da Interface Fluente é especialmente útil quando em conjunção com o padrão Builder, pois quase sempre estamos interessados em passar alguma informação – várias, na realidade – para ele e raramente queremos pedir alguma coisa dele. O único método que não segue esta regra é aquele que retorna o produto já montado. Contudo, nesse ponto, a nossa configuração do objeto montador já terminou.

Builder, QueryObject e DSL

Uma das grandes utilidades do padrão Builder é utilizá-lo para construir estruturas que implementem o padrão Composite Object ou que sejam, de alguma forma, composições de objetos. Um exemplo importante são as implementações de Query Object . O objetivo de um Query Object é ser um objeto que representa toda a informação necessária para impor critérios de pesquisas. Uma String com SQL é uma forma 'primitiva' e muito acoplada de Query Object. Os Query Object de hoje correspondem a objetos especiais – conhecidos como Criteria – que serão interpretados para criar o SQL ou interpretados diretamente para obter os resultados da pesquisa. Um montador de objetos que implementam QueryObject seria algo como:

Criteria criteria = CriteriaBuilder.search (Customer.class)
	.and ( "active" ).eq (true)
	.and ( "name" ).endsWith ( "io" )
	.orderBy ( "name" ).asc()
	.build ();

Com este padrão podemos criar mini linguagens especificas ao dominio (Domain Specific Language - DSL) em causa. Aqui o dominio seria criar critérios de pesquisa, mas pode ser usado em qualquer dominio.

Interface Progressiva

Vimos que a interface fluente é conseguida retornando o próprio objeto no método de forma a poder encadear as ações.  Contudo, repare que a frase  `.and("active").eq(true)`  atua como uma unidade e não deveria ser possível escrever    `.and("active").and("name")` . Não faria sentido explicitar o nome do campo sem dizer qual valor queremos para ele.  Para conseguir coesão durante o encadeamento de chamadas  o método  `and()`  não retorna o objeto `Builder` de `Criteria` original  sob o qual foi invocado, mas sim um novo objeto `Builder` para aquela "frase" de filtro. Apenas quando a frase  tiver todos os seus componentes é que ela é adicionada internamente ao `Builder` original e voltamos a ele `Builder` podendo invocar   `and()`  novamente ou outro método como  `orderBy`  (no qual usamos a mesma técnica para invocar  `asc()` )

Esta técnica é chamada de Interface Progressiva[[2]]. A ideia é que o contrato ( a interface, o conjunto de métodos disponiveis) é alterado sempre que queremos garantir algum tipo de coesão ou caminho a seguir pelo programador em relação ao objeto Builder . Esta técnica além de útil - nos IDE de hoje com recursos de autocompletação - ajuda a garantir tipagem forte ao longo de todo o processo de montagem além de grantir que regras complexas ou burocráticas serão cumpridas pelo programador.

Exemplos na API padrão

O padrão Builder está presente originalmente na API padrão na classe StringBuffer que é usada para construir objetos String . Com a introdução da classe StringBuilder foi criada a interface Appendable que expõe o método append() que é responsável por parte da fluência destes objetos. Essa interface foi aplicada a objetos como PrintWriter tornando mais simples a operação de concatenar `String`s.

Discussão

O Builder acaba fazendo o papel de 'linguagem de programação' específica para o domínio do objeto contruído já que os seus métodos mapeiam condições comuns que queremos usar para aquele tipo de objeto. Torna-se, portanto, claro que ao implementar um objeto Builder o ganho é tanto maior quanto mais a interface fluente se aproximar da forma com o programador pensa quando pretende montar o objeto produto.

Estas interfaces fluentes especificamente úteis em certo domínio ganharam a denominação de Linguagens Específicas de Domínio (DSL em inglês). A Interface Fluente e um Builder são características comuns às DSL quando implementadas em Java. Outros tipos possíveis que incluem o uso de linguagens realmente específicas (com seu compilador e interpretador) são também uma hipótese – e até possível de integrar com java via Java Scripting Framework - mas um Builder com interface fluente é muito mais simples de construir e muito útil mesmo assim.

O objectivo do padrão Builder é ajudar o programador na tarefa de montar/configurar objetos. Contudo isto não significa que o objeto não possa ser montado ou configurado à mão, da forma tradicional, invocando cada um dos seus modificadores. Também não significa que o objeto Builder tenha algum acesso particular aos atributos do objeto produto. Embora isso possa acontecer, isso implicaria em que o produto poderia apenas ser criado usando o objeto Builder transformando-o num misto de Factory o que não é o objetivo aqui.

Às vezes não é simples destinguir entre um objeto que implementa o padrão Builder de um que implementa o padrão Factory pois as diferenças são sutis. Contudo é normal para um objeto Builder existirem diversos métodos que podem ser invocados ou não e um único método cuja invocação é obrigatório e que retorna o objeto produto devidamente montado enquanto que num objeto Factory existir apenas um método com uma série de parâmetros, possivelmente até várias sobrecargas desse método. Normalmente um Factory não tem estado usando os parâmetros para construir o objeto, enquanto um Builder vai acumulando parâmetros para produzir o produto no final. Por vezes a forma como o programador desenha estes objetos pode iludir a sua finalidade ou o padrão que estão sendo usado o que apenas leva à confusão.

Enquanto o padrão Factory visa substituir o papel do construtor da classe, o padrão Builder visa principalmente simplificar a invocação dos métodos modificadores do objeto depois que o construtor da classe já foi invocado.

Padrões relacionados

O padrão Builder é facilmente relacionado ao padrão Method Channing já que é extremamente útil para a sua implementação, embora não sendo obrigatório.

O padrão Builder é também facilmente relacionado ao padrão Factory devido à semelhança de objetivos.

Bibliografia

[1] Steven John Metsker Design Patterns Java Workbook: Addison-Wesley Professional (2002)

[3] Ralph Johnson, Erich Gamma, John Vlissides, Richard Helm Design Patterns: Elements of Reusable Object-Oriented Software: Addison-Wesley (2005)

Scroll to Top