Patrón Builder en Go
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 80 de 107.
El Patrón Builder separa la construcción de un objeto complejo de su representación. A diferencia del patrón Functional Options, el Builder utiliza una estructura (struct) de constructor dedicada con métodos que configuran el objeto paso a paso.
Aquí se explica cómo implementar un constructor (builder) para una solicitud 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 devuelve el puntero del constructor, permitiendo el encadenamiento de métodos. El método Build() finaliza y devuelve el objeto construido:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()El patrón Builder sobresale cuando los objetos requieren múltiples pasos de configuración o cuando se desea imponer una secuencia de construcción específica. Es particularmente útil para crear objetos inmutables o cuando el proceso de construcción en sí mismo necesita validación.
Desafío
Fácil¡Vamos a construir un compositor de mensajes de correo electrónico utilizando el Builder Pattern! Los mensajes de correo electrónico tienen muchos componentes opcionales (destinatarios, CC, asunto, cuerpo, archivos adjuntos), lo que los convierte en un candidato perfecto para la construcción paso a paso con encadenamiento de métodos.
Organizarás tu código en dos archivos:
email.go: Define tu estructura de correo electrónico y su constructor.Crea un struct
Emailcon estos campos:from(string)to(slice de strings)cc(slice de strings)subject(string)body(string)
Crea un struct
EmailBuilderque contenga un puntero a un Email que se está construyendo. ImplementaNewEmailBuilder()que devuelva un nuevo builder con un Email inicializado (slices vacíos paratoycc).Agrega estos métodos al builder, cada uno devolviendo
*EmailBuilderpara el encadenamiento:From(address string)- establece el remitenteTo(address string)- agrega un destinatario al slicetoCC(address string)- agrega un destinatario al sliceccSubject(subject string)- establece la línea del asuntoBody(body string)- establece el cuerpo del correo electrónico
Agrega un método
Build()que devuelva el*Emailconstruido.Finalmente, agrega un método
Summary()en Email que devuelva un string en este formato:From: [from] To: [comma-separated to addresses] CC: [comma-separated cc addresses or "none"] Subject: [subject] Body: [body]main.go: Construye y muestra mensajes de correo electrónico utilizando el builder.Lee la dirección del remitente, luego lee un recuento de destinatarios y cada dirección de destinatario. A continuación, lee un recuento de destinatarios de CC y cada dirección de CC. Finalmente, lee el asunto y el cuerpo.
Utiliza el EmailBuilder con encadenamiento de métodos para construir el correo electrónico, luego imprime su resumen.
Se proporcionarán las siguientes entradas:
- Línea 1: Dirección del remitente
- Línea 2: Número de destinatarios
- Líneas siguientes: Cada dirección de destinatario
- Siguiente línea: Número de destinatarios de CC
- Líneas siguientes: Cada dirección de CC
- Siguiente línea: Asunto
- Línea final: Cuerpo
Por ejemplo, dado:
alice@company.com
2
bob@company.com
carol@company.com
1
manager@company.com
Weekly Report
Please find the weekly report attached.Tu salida debería 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.Y dado:
support@service.com
1
customer@email.com
0
Your Ticket Update
Your support ticket has been resolved.Tu salida debería ser:
From: support@service.com
To: customer@email.com
CC: none
Subject: Your Ticket Update
Body: Your support ticket has been resolved.Observa cómo el builder te permite agregar múltiples destinatarios uno a la vez a través de llamadas repetidas a To(), y cómo el patrón mantiene el proceso de construcción limpio y legible incluso con muchos campos opcionales.
Hoja de referencia
El Patrón Builder separa la construcción de un objeto complejo de su representación utilizando una estructura builder dedicada con métodos que configuran el objeto paso a paso.
Estructura 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),
},
}
}Los métodos del builder devuelven el puntero del builder para permitir el encadenamiento 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
}El método Build() finaliza y devuelve el objeto construido:
func (b *RequestBuilder) Build() *Request {
return b.request
}Uso con encadenamiento de métodos:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()El patrón Builder es útil cuando los objetos requieren múltiples pasos de configuración, cuando se impone una secuencia de construcción específica, para crear objetos inmutables o cuando el proceso de construcción necesita validación.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer la dirección del remitente
from, _ := reader.ReadString('\n')
from = strings.TrimSpace(from)
// Leer el número de destinatarios
toCountStr, _ := reader.ReadString('\n')
toCount, _ := strconv.Atoi(strings.TrimSpace(toCountStr))
// Leer cada dirección de destinatario
toAddresses := make([]string, toCount)
for i := 0; i < toCount; i++ {
addr, _ := reader.ReadString('\n')
toAddresses[i] = strings.TrimSpace(addr)
}
// Leer el número de destinatarios en CC
ccCountStr, _ := reader.ReadString('\n')
ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))
// Leer cada dirección en CC
ccAddresses := make([]string, ccCount)
for i := 0; i < ccCount; i++ {
addr, _ := reader.ReadString('\n')
ccAddresses[i] = strings.TrimSpace(addr)
}
// Leer el asunto
subject, _ := reader.ReadString('\n')
subject = strings.TrimSpace(subject)
// Leer el cuerpo
body, _ := reader.ReadString('\n')
body = strings.TrimSpace(body)
// TODO: Usar el EmailBuilder con encadenamiento de métodos para construir el correo electrónico
// Comenzar con NewEmailBuilder(), luego encadenar From(), To(), CC(), Subject(), Body()
// Finalmente llamar a Build() para obtener el Email
// TODO: Imprimir el resumen del correo electrónico usando el método Summary()
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool12Conceptos avanzados de POO
Patrón Functional OptionsPatrón Builder en GoEncadenamiento de métodosAlias de tipos vs. definicionesFundamentos de ReflectionDescripción general de generación de código