Introdução ao Padrão Builder
O padrão Builder é um padrão de projeto criacional que permite a construção de objetos complexos passo a passo. Ele separa a construção de um objeto complexo de sua representação, permitindo que o mesmo processo de construção crie diferentes representações.
Problema a Resolver
Considere uma classe de sistema que precisa ser configurada com múltiplos parâmetros:
- processador (obrigatório)
- memoriaRAM (obrigatório)
- portasUSB (opcional)
- teclado (opcional)
- monitor (opcional)
Enquanto dois parâmetros são obrigatórios, os outros três são opcionais.
Abordagens Tradicionais
Construtor Encadeado
Padrão JavaBean
Limitações
Abordagem 1
A principal desvantagem é a dificuldade de uso e leitura. Ao invocar um construtor de classe, é necessário primeiro decidir qual usar, pois os parâmetros são numerosos e de tipos diferentes, o que facilita a confusão.
Abordagem 2
Durante o processo de construção, o estado do objeto pode ser alterado acidentalmente, causando erros. Como as propriedades da classe são definidas em etapas separadas, é mais propenso a erros.
Características
Em desenvolvimento de software, frequentemente é necessário criar um objeto complexo que consiste em várias subpartes combinadas em etapas específicas.
Os produtos são compostos por várias partes, que podem ser selecionadas de forma flexível, mas o processo de criação é semelhante. Essa criação não pode ser descrita adequadamente pelos padrões de fábrica anteriores, e apenas o padrão Builder descreve bem a criação desse tipo de produto.
Implementação do Padrão Builder
Primeiro passo: Criar a classe de produto, no nosso caso, a classe Sistema
/**
* Produto Sistema
*
*/
public class Sistema {
private final String processador;// obrigatório
private final String memoriaRAM;// obrigatório
private int portasUSB;// opcional
private String teclado;// opcional
private String monitor;// opcional
public Sistema(String processador, String memoriaRAM) {
this.processador = processador;
this.memoriaRAM = memoriaRAM;
}
public void setPortasUSB(int portasUSB) {
this.portasUSB = portasUSB;
}
public void setTeclado(String teclado) {
this.teclado = teclado;
}
public void setMonitor(String monitor) {
this.monitor = monitor;
}
@Override
public String toString() {
return "Sistema{" + "processador='" + processador + '\'' + ", memoriaRAM='" + memoriaRAM + '\'' +
", portasUSB=" + portasUSB + ", teclado='" + teclado + '\'' + ", monitor='" + monitor + '\'' + '}';
}
}
Segundo passo: Criar a classe construtora abstrata
public abstract class SistemaBuilder {
public abstract void definirPortasUSB(int portasUSB);
public abstract void definirTeclado(String teclado);
public abstract void definirMonitor(String monitor);
public abstract Sistema construirSistema();
}
Terceiro passo: Criar a classe construtora concreta
public class SistemaCompletoBuilder extends SistemaBuilder{
private Sistema sistema;
public SistemaCompletoBuilder(String processador, String memoriaRAM) {
sistema = new Sistema(processador, memoriaRAM);
}
@Override
public void definirPortasUSB(int portasUSB) {
sistema.setPortasUSB(portasUSB);
}
@Override
public void definirTeclado(String teclado) {
sistema.setTeclado(teclado);
}
@Override
public void definirMonitor(String monitor) {
sistema.setMonitor(monitor);
}
@Override
public Sistema construirSistema() {
return sistema;
}
}
Quarto passo: Criar a classe diretora
public class ConstrutorSistema {
private SistemaBuilder builder;
public void setBuilder(SistemaBuilder builder) {
this.builder = builder;
}
public Sistema montarSistema(int portasUSB, String monitor, String teclado){
builder.definirPortasUSB(portasUSB);
builder.definirMonitor(monitor);
builder.definirTeclado(teclado);
return builder.construirSistema();
}
}
Definição
O padrão Builder é um padrão de projeto que separa a construção de um objeto complexo de sua representação, permitindo que o mesmo processo de construção crie diferentes representações.
Ele divide um objeto complexo em vários objetos simples e os constrói passo a passo. Ou seja, as partes componentes do produto são fixas, mas cada parte pode ser selecionada de forma flexível.
Diferenças entre Padrão Builder e Padrão Fábrica
Padrão Fábrica
- O padrão Fábrica concentra-se na criação de produtos
- Os produtos criados pelo padrão Fábrica são todos idênticos
- O foco do padrão Fábrica é apenas criar o objeto
Padrão Builder
- O padrão Builder concentra-se na ordem das chamadas de método
- O padrão Builder pode criar produtos complexos compostos por várias partes complexas
- O padrão Builder não apenas cria o produto, mas também sabe de quais partes o produto é composto
Componentes do Padrão Builder
- Produto (Product): Um objeto de produto específico.
- Construtor Abstrato (Builder): Uma interface/abstração que especifica as interfaces para criar os vários componentes de um objeto Produto.
- Construtor Concreto (ConcreteBuilder): Implementa a interface, construindo e montando os vários componentes.
- Diretor (Director): Um objeto que usa a interface Builder.
- É principalmente usado para criar um objeto complexo.
- Funções
- Isola o cliente do processo de produção do objeto
- Controla o processo de produção do objeto Produto.
Vantagens do Padrão Builder
- Ele isola a construção específica e o método de montagem, tornnado o processo de construção separado da implementação concreta.
- Ele suporta a construção de objetos complexos e pode controlar a ordem de construção de objetos complexos.
- Permite que o usuário altere independentemente a representação interna de um objeto.
- Fornece um método de construção flexível que pode criar objetos quando o usuário não está seguro sobre os detalhes finais do produto.
- Pode evitar eficazmente que o usuário danifique a estrutura do produto durante o processo de construção.
Em termos simples:
- Encapsulamento eficaz, construção e representação separadas.
- Boa extensibilidade, os construtores específicos são independentes uns dos outros, favorecendo o desacoplamento do sistema.
- O cliente não precisa saber os detalhes da composição interna do produto, o construtor pode refinar gradualmente o processo de criação sem afetar outros módulos, facilitando o controle de riscos detalhados.
Desvantagens do Padrão Builder
- Gera objetos Builder excessivos
- Se a estrutura interna do produto mudar, todos os construtores precisam ser modificados, o que tem um custo maior
Segunda Implementação do Padrão Builder
Para resolver o problema da criação de objetos Builder excessivos, podemos usar uma abordagem alternativa.
Na classe ConfiguracaoMaquina, criamos uma classe interna estática Builder, e copiamos todos os parâmetros da classe ConfiguracaoMaquina para a classe Builder.
Exemplo:
/**
* Construção de dados usando classe interna estática
*
*/
public class ConfiguracaoMaquina {
private final String processador;// obrigatório
private final String memoriaRAM;// obrigatório
private final int portasUSB;// opcional
private final String teclado;// opcional
private final String monitor;// opcional
@Override
public String toString() {
return "ConfiguracaoMaquina{" + "processador='" + processador + '\'' +
", memoriaRAM='" + memoriaRAM + '\'' + ", portasUSB=" + portasUSB +
", teclado='" + teclado + '\'' + ", monitor='" + monitor + '\'' + '}';
}
private ConfiguracaoMaquina(Builder builder) {
this.processador = builder.processador;
this.memoriaRAM = builder.memoriaRAM;
this.portasUSB = builder.portasUSB;
this.teclado = builder.teclado;
this.monitor = builder.monitor;
}
public static class Builder {
private final String processador;// obrigatório
private final String memoriaRAM;// obrigatório
private int portasUSB;// opcional
private String teclado;// opcional
private String monitor;// opcional
public Builder(String processador, String memoriaRAM) {
this.processador = processador;
this.memoriaRAM = memoriaRAM;
}
public Builder comPortasUSB(int portasUSB) {
this.portasUSB = portasUSB;
return this;
}
public Builder comTeclado(String teclado) {
this.teclado = teclado;
return this;
}
public Builder comMonitor(String monitor) {
this.monitor = monitor;
return this;
}
public ConfiguracaoMaquina construir() {
return new ConfiguracaoMaquina(this);
}
}
}
Casos de Uso
Quando um objeto tem uma estrutura interna muito complexa (muitas propriedades) E você deseja separar a criação de objetos complexos de seu uso
Resumo
O padrão Builder é usado para criar objetos compostos complexos
Análise de Código Fonte
StringBuilder
- A classe StringBuilder do JDK fornece o método append(), que é uma forma de criar objetos de forma encadeada. Ele abre o processo de construção e, ao final, chama o método toString() para obter um objeto completo.
SqlSessionFactoryBuilder
- A classe SqlSessionFactoryBuilder no MyBatis utiliza o padrão Builder. No MyBatis, o SqlSessionFactory é produzido pelo SqlSessionFactoryBuilder.
- A classe XMLConfigBuilder é responsável pela criação e montagem de cada componente do Configuration. Todo o processo de montagem é o seguinte:
- XMLConfigBuilder é responsável por criar o objeto complexo Configuration, na verdade, ele representa o papel de construtor específico.
- SqlSessionFactoryBuilder apenas encapsula a construção da instância SqlSessionFactory, simplificando o processo de construção, o que é essencialmente o padrão Builder.