Padrão Builder em Go
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 80 de 107.
O Padrão Builder separa a construção de um objeto complexo de sua representação. Ao contrário do padrão Functional Options, o Builder utiliza uma struct builder dedicada com métodos que configuram o objeto passo a passo.
Aqui está como implementar um builder para uma requisição HTTP:
type Request struct {
method string
url string
headers map[string]string
body string
}
type RequestBuilder struct {
request *Request
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &Request{
headers: make(map[string]string),
},
}
}
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.request.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.request.url = u
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}
func (b *RequestBuilder) Build() *Request {
return b.request
}Cada método retorna o ponteiro do builder, permitindo o encadeamento de métodos. O método Build() finaliza e retorna o objeto construído:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()O padrão Builder se destaca quando os objetos exigem várias etapas de configuração ou quando você deseja impor uma sequência de construção específica. Ele é particularmente útil para criar objetos imutáveis ou quando o próprio processo de construção precisa de validação.
Desafio
FácilVamos construir um compositor de mensagens de e-mail usando o Builder Pattern! Mensagens de e-mail têm muitos componentes opcionais — destinatários, CC, assunto, corpo, anexos — tornando-as candidatas perfeitas para a construção passo a passo com encadeamento de métodos.
Você organizará seu código em dois arquivos:
email.go: Defina sua estrutura de e-mail e seu builder.Crie uma struct
Emailcom estes campos:from(string)to(slice de strings)cc(slice de strings)subject(string)body(string)
Crie uma struct
EmailBuilderque contém um ponteiro para um Email sendo construído. ImplementeNewEmailBuilder()que retorna um novo builder com um Email inicializado (slices vazios paratoecc).Adicione estes métodos ao builder, cada um retornando
*EmailBuilderpara encadeamento:From(address string)- define o remetenteTo(address string)- adiciona um destinatário ao slicetoCC(address string)- adiciona um destinatário ao sliceccSubject(subject string)- define a linha de assuntoBody(body string)- define o corpo do e-mail
Adicione um método
Build()que retorna o*Emailconstruído.Finalmente, adicione um método
Summary()no Email que retorna uma string neste formato:From: [from] To: [comma-separated to addresses] CC: [comma-separated cc addresses or "none"] Subject: [subject] Body: [body]main.go: Construa e exiba mensagens de e-mail usando o builder.Leia o endereço do remetente, depois leia uma contagem de destinatários e cada endereço de destinatário. Em seguida, leia uma contagem de destinatários CC e cada endereço CC. Finalmente, leia o assunto e o corpo.
Use o EmailBuilder com encadeamento de métodos para construir o e-mail e, em seguida, imprima seu resumo.
As seguintes entradas serão fornecidas:
- Linha 1: Endereço do remetente
- Linha 2: Número de destinatários
- Linhas seguintes: Cada endereço de destinatário
- Próxima linha: Número de destinatários CC
- Linhas seguintes: Cada endereço CC
- Próxima linha: Assunto
- Linha final: Corpo
Por exemplo, dado:
alice@company.com
2
bob@company.com
carol@company.com
1
manager@company.com
Weekly Report
Please find the weekly report attached.Sua saída deve ser:
From: alice@company.com
To: bob@company.com, carol@company.com
CC: manager@company.com
Subject: Weekly Report
Body: Please find the weekly report attached.E dado:
support@service.com
1
customer@email.com
0
Your Ticket Update
Your support ticket has been resolved.Sua saída deve ser:
From: support@service.com
To: customer@email.com
CC: none
Subject: Your Ticket Update
Body: Your support ticket has been resolved.Observe como o builder permite adicionar vários destinatários um de cada vez através de chamadas repetidas a To(), e como o padrão mantém o processo de construção limpo e legível, mesmo com muitos campos opcionais.
Folha de consulta
O Builder Pattern separa a construção de um objeto complexo de sua representação usando uma struct builder dedicada com métodos que configuram o objeto passo a passo.
Estrutura básica:
type Request struct {
method string
url string
headers map[string]string
body string
}
type RequestBuilder struct {
request *Request
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &Request{
headers: make(map[string]string),
},
}
}Os métodos do builder retornam o ponteiro do builder para permitir o encadeamento de métodos:
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.request.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.request.url = u
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}O método Build() finaliza e retorna o objeto construído:
func (b *RequestBuilder) Build() *Request {
return b.request
}Uso com encadeamento de métodos:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()O padrão Builder é útil quando objetos exigem várias etapas de configuração, ao impor uma sequência de construção específica, para criar objetos imutáveis ou quando o processo de construção precisa de validação.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Ler o endereço do remetente
from, _ := reader.ReadString('\n')
from = strings.TrimSpace(from)
// Ler o número de destinatários
toCountStr, _ := reader.ReadString('\n')
toCount, _ := strconv.Atoi(strings.TrimSpace(toCountStr))
// Ler cada endereço de destinatário
toAddresses := make([]string, toCount)
for i := 0; i < toCount; i++ {
addr, _ := reader.ReadString('\n')
toAddresses[i] = strings.TrimSpace(addr)
}
// Ler o número de destinatários CC
ccCountStr, _ := reader.ReadString('\n')
ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))
// Ler cada endereço CC
ccAddresses := make([]string, ccCount)
for i := 0; i < ccCount; i++ {
addr, _ := reader.ReadString('\n')
ccAddresses[i] = strings.TrimSpace(addr)
}
// Ler o assunto
subject, _ := reader.ReadString('\n')
subject = strings.TrimSpace(subject)
// Ler o corpo
body, _ := reader.ReadString('\n')
body = strings.TrimSpace(body)
// TODO: Use o EmailBuilder com encadeamento de métodos para construir o e-mail
// Comece com NewEmailBuilder(), depois encadeie From(), To(), CC(), Subject(), Body()
// Finalmente, chame Build() para obter o Email
// TODO: Imprima o resumo do e-mail usando o método Summary()
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool12Conceitos Avançados de OOP
Padrão Functional OptionsPadrão Builder em GoEncadeamento de MétodosType Aliases vs DefiniçõesFundamentos de ReflectionVisão Geral de Geração de Código