Menu
Coddy logo textTech

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.

challenge icon

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 Email con estos campos:

    • from (string)
    • to (slice de strings)
    • cc (slice de strings)
    • subject (string)
    • body (string)

    Crea un struct EmailBuilder que contenga un puntero a un Email que se está construyendo. Implementa NewEmailBuilder() que devuelva un nuevo builder con un Email inicializado (slices vacíos para to y cc).

    Agrega estos métodos al builder, cada uno devolviendo *EmailBuilder para el encadenamiento:

    • From(address string) - establece el remitente
    • To(address string) - agrega un destinatario al slice to
    • CC(address string) - agrega un destinatario al slice cc
    • Subject(subject string) - establece la línea del asunto
    • Body(body string) - establece el cuerpo del correo electrónico

    Agrega un método Build() que devuelva el *Email construido.

    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()
}
quiz iconPonte a prueba

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