Padrão Builder: Construção Passo a Passo de Objetos Complexos

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:

  1. Em um fast-food, itens como hambúrguer, refrigerante e batata frita são individuais, mas sua combinação forma diferentes "combos";
  2. A classe StringBuilder em 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:

  1. Definir as parttes que compõem o produto (gabinete, monitor, teclado, caixas de som);
  2. Criar a classe responsável pela montagem;
  3. Adicionar cada componente progressivamente;
  4. 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.

Tags: Builder Pattern design patterns java MyBatis Software Architecture

Publicado em 7-13 01:12