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 de Opciones Funcionales, Builder utiliza una estructura de construcción dedicada con métodos que configuran el objeto paso a paso.
A continuación, se muestra cómo implementar un 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, lo que permite encadenar 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 destaca cuando los objetos requieren varios pasos de configuración o cuando quieres imponer una secuencia de construcción específica. Es especialmente útil para crear objetos inmutables o cuando el propio proceso de construcción necesita validación.
Desafío
Fácil¡Construyamos un compositor de mensajes de correo electrónico usando el patrón Builder! Los mensajes de correo electrónico tienen muchos componentes opcionales, como destinatarios, CC, asunto, cuerpo y archivos adjuntos, lo que los convierte en candidatos perfectos para una construcción paso a paso mediante el encadenamiento de métodos.
Organizarás tu código en dos archivos:
email.go: Define la estructura de tu correo electrónico y su builder.Crea una estructura
Emailcon estos campos:from(string)to(slice de strings)cc(slice de strings)subject(string)body(string)
Crea una estructura
EmailBuilderque contenga un puntero a un Email en construcción. ImplementaNewEmailBuilder(), que devuelva un builder nuevo con un Email inicializado (slices vacíos paratoycc).Añade estos métodos del builder, cada uno devolviendo
*EmailBuilderpara permitir el encadenamiento:From(address string)- establece el remitenteTo(address string)- añade un destinatario al slicetoCC(address string)- añade un destinatario al sliceccSubject(subject string)- establece el asuntoBody(body string)- establece el cuerpo del correo electrónico
Añade un método
Build()que devuelva el*Emailconstruido.Finalmente, añade un método
Summary()en Email que devuelva una cadena con 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 usando el builder.Lee la dirección del remitente, después lee la cantidad de destinatarios y cada dirección de destinatario. A continuación, lee la cantidad de destinatarios en CC y cada dirección de CC. Finalmente, lee el asunto y el cuerpo.
Usa EmailBuilder con el encadenamiento de métodos para construir el correo electrónico y, después, 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
- Línea siguiente: Número de destinatarios en CC
- Líneas siguientes: Cada dirección de CC
- Línea siguiente: Asunto
- Línea final: Cuerpo
Por ejemplo, dada la siguiente entrada:
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 dada la siguiente entrada:
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 añadir varios destinatarios de uno en uno mediante llamadas repetidas a To(), y cómo el patrón mantiene el proceso de construcción limpio y legible incluso con muchos campos opcionales.
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 de 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: Usa el EmailBuilder con encadenamiento de métodos para construir el email
// Empieza con NewEmailBuilder(), luego encadena From(), To(), CC(), Subject(), Body()
// Finalmente llama a Build() para obtener el Email
// TODO: Imprime el resumen del email 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ódigoPractica por tu cuenta: Compilador de Go online