Conceito
O padrão Builder (Construtor) é um padrão de projeto criacional que permite a construção de objetos complexos por meio de uma sequência de etapas incrementais. Em vez de tentar criar tudo de uma vez em um construtor, um objeto Builder coordena a montagem gradual das partes que compõem o produto final.
O princípio central é separar o processo de construção da representação final do objeto. Dessa forma, o mesmo conjunto de etapas construtivas pode gerar representações distintas conforme a necessidade.
Exemplos práticos:
- Em um fast-food, itens como hambúrguer, refrigerante e batata frita são individuais, mas sua combinação forma diferentes "combos";
- A classe
StringBuilderem Java, que acumula caracteres progressivamente.
Quando Utilizar
A escolha entre Factory Method e Builder depende da complexidade do objeto. O Factory Method é mais adequado quando o produto pode ser criado em uma única operação. Já o Builder brilha quando a construção exige múltiplas etapas e o produto é composto por diversas partes independentes — ou até mesmo quando apenas parte do objeto precisa ser montada.
Exemplo Prático: Montagem de um Computador
Para demonstrar o padrão, vamos modelar a montagem de um computador. A ideia é decompor o produto em componentes e usar um Builder para montá-los:
- Definir as parttes que compõem o produto (gabinete, monitor, teclado, caixas de som);
- Criar a classe responsável pela montagem;
- Adicionar cada componente progressivamente;
- Obter o produto final totalmente montado.
// Produto complexo
public class DesktopComputer {
private String cabinet;
private String display;
private String inputDevice;
private String audioDevice;
public void setCabinet(String cabinet) { this.cabinet = cabinet; }
public void setDisplay(String display) { this.display = display; }
public void setInputDevice(String inputDevice) { this.inputDevice = inputDevice; }
public void setAudioDevice(String audioDevice) { this.audioDevice = audioDevice; }
@Override
public String toString() {
return String.format("DesktopComputer{cabinet='%s', display='%s', input='%s', audio='%s'}",
cabinet, display, inputDevice, audioDevice);
}
}
// Interface do construtor
public interface DesktopAssembler {
DesktopAssembler mountCabinet();
DesktopAssembler mountDisplay();
DesktopAssembler mountInputDevice();
DesktopAssembler mountAudioDevice();
DesktopComputer assemble();
}
// Implementação concreta do construtor
public class GamingDesktopAssembler implements DesktopAssembler {
private DesktopComputer computer;
public GamingDesktopAssembler() {
this.computer = new DesktopComputer();
}
@Override
public DesktopAssembler mountCabinet() {
computer.setCabinet("Gabinete Gamer RGB");
return this;
}
@Override
public DesktopAssembler mountDisplay() {
computer.setDisplay("Monitor 27 polegadas 144Hz");
return this;
}
@Override
public DesktopAssembler mountInputDevice() {
computer.setInputDevice("Teclado Mecânico");
return this;
}
@Override
public DesktopAssembler mountAudioDevice() {
computer.setAudioDevice("Headset 7.1 Surround");
return this;
}
@Override
public DesktopComputer assemble() {
return this.computer;
}
}
// Diretor (opcional) que orquestra a montagem
public class AssemblyDirector {
public DesktopComputer buildGamingSetup(DesktopAssembler assembler) {
return assembler.mountCabinet()
.mountDisplay()
.mountInputDevice()
.mountAudioDevice()
.assemble();
}
}
// Uso
public class Demo {
public static void main(String[] args) {
AssemblyDirector director = new AssemblyDirector();
DesktopComputer pc = director.buildGamingSetup(new GamingDesktopAssembler());
System.out.println(pc);
}
}
Caso Real: SqlSessionFactory no MyBatis
A inicialização do MyBatis é um exemplo robusto do Builder em ação. O objeto SqlSessionFactory depende de configurações complexas que não cabem em um único construtor. Por isso, o framework delega essa responsabilidade a uma hierarquia de Builders.
O SqlSessionFactoryBuilder é o ponto de entrada. Ele invoca o XMLConfigBuilder, responsável por interpretar o arquivo mybatis-config.xml e construir o objeto central Configuration. Esse processo envolve a leitura e interpretação de diversos elementos XML:
private void loadConfiguration(XNode rootNode) {
try {
// Carrega propriedades externas
resolveProperties(rootNode.evalNode("properties"));
// Processa configurações
Properties config = parseSettings(rootNode.evalNode("settings"));
// Registra VFS customizados
registerCustomVfs(config);
// Carrega aliases de tipos
loadTypeAliases(rootNode.evalNode("typeAliases"));
// Registra plugins
registerPlugins(rootNode.evalNode("plugins"));
// Configura fábricas de objetos
configureObjectFactory(rootNode.evalNode("objectFactory"));
configureObjectWrapperFactory(rootNode.evalNode("objectWrapperFactory"));
configureReflectorFactory(rootNode.evalNode("reflectorFactory"));
// Aplica configurações no objeto Configuration
applySettings(config);
// Processa ambiente e provedor de banco
processEnvironments(rootNode.evalNode("environments"));
processDatabaseIdProvider(rootNode.evalNode("databaseIdProvider"));
} catch (Exception ex) {
throw new BuilderException("Falha ao carregar configuração", ex);
}
}
Posteriormente, o XMLConfigBuilder aciona o XMLMapperBuilder para processar os arquivos de mapeamento. Por sua vez, o XMLMapperBuilder delega a leitura das instruções SQL ao XMLStatementBuilder, formando uma cadeia de construtores:
// Processa mapeamentos SQL
private void processMappers(XNode node) {
if (node != null) {
for (XNode mapperNode : node.getChildren()) {
String resource = mapperNode.getStringAttribute("resource");
String url = mapperNode.getStringAttribute("url");
String mapperClass = mapperNode.getStringAttribute("class");
if (resource != null) {
mapperParser.parseResource(resource);
} else if (url != null) {
mapperParser.parseUrl(url);
} else if (mapperClass != null) {
// Registra via classe Java
configuration.addMapper(Class.forName(mapperClass));
}
}
}
}
O padrão de comportamento entre todos esses Builders é consistente: cada um lê uma fonte de dados, interpreta expressões XPath, realiza validações semânticas, instancia objetos via reflexão e os armazena em caches internos. Esse volume de operações torna impraticável concentrar tudo em um único construtor tradicional, justificando plenamente a adoção do Builder como solução arquitetural.