Menu
Coddy logo textTech

Builder Pattern in Go

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 80 of 107.

The Builder Pattern separates the construction of a complex object from its representation. Unlike the Functional Options pattern, the Builder uses a dedicated builder struct with methods that configure the object step by step.

Here's how to implement a builder for an HTTP request:

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
}

Each method returns the builder pointer, enabling method chaining. The Build() method finalizes and returns the constructed object:

req := NewRequestBuilder().
    Method("POST").
    URL("https://api.example.com").
    Header("Content-Type", "application/json").
    Build()

The Builder pattern excels when objects require multiple configuration steps or when you want to enforce a specific construction sequence. It's particularly useful for creating immutable objects or when the construction process itself needs validation.

challenge icon

Challenge

Easy

Let's build an email message composer using the Builder Pattern! Email messages have many optional components—recipients, CC, subject, body, attachments—making them a perfect candidate for step-by-step construction with method chaining.

You'll organize your code across two files:

  • email.go: Define your email structure and its builder.

    Create an Email struct with these fields:

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

    Create an EmailBuilder struct that holds a pointer to an Email being constructed. Implement NewEmailBuilder() that returns a new builder with an initialized Email (empty slices for to and cc).

    Add these builder methods, each returning *EmailBuilder for chaining:

    • From(address string) - sets the sender
    • To(address string) - adds a recipient to the to slice
    • CC(address string) - adds a recipient to the cc slice
    • Subject(subject string) - sets the subject line
    • Body(body string) - sets the email body

    Add a Build() method that returns the constructed *Email.

    Finally, add a Summary() method on Email that returns a string in this format:

    From: [from]
    To: [comma-separated to addresses]
    CC: [comma-separated cc addresses or "none"]
    Subject: [subject]
    Body: [body]
  • main.go: Build and display email messages using the builder.

    Read the sender address, then read a count of recipients and each recipient address. Next, read a count of CC recipients and each CC address. Finally, read the subject and body.

    Use the EmailBuilder with method chaining to construct the email, then print its summary.

The following inputs will be provided:

  • Line 1: Sender address
  • Line 2: Number of recipients
  • Following lines: Each recipient address
  • Next line: Number of CC recipients
  • Following lines: Each CC address
  • Next line: Subject
  • Final line: Body

For example, given:

alice@company.com
2
bob@company.com
carol@company.com
1
manager@company.com
Weekly Report
Please find the weekly report attached.

Your output should be:

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.

And given:

support@service.com
1
customer@email.com
0
Your Ticket Update
Your support ticket has been resolved.

Your output should be:

From: support@service.com
To: customer@email.com
CC: none
Subject: Your Ticket Update
Body: Your support ticket has been resolved.

Notice how the builder lets you add multiple recipients one at a time through repeated To() calls, and how the pattern keeps the construction process clean and readable even with many optional fields.

Cheat sheet

The Builder Pattern separates the construction of a complex object from its representation using a dedicated builder struct with methods that configure the object step by step.

Basic structure:

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),
        },
    }
}

Builder methods return the builder pointer to enable method chaining:

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
}

The Build() method finalizes and returns the constructed object:

func (b *RequestBuilder) Build() *Request {
    return b.request
}

Usage with method chaining:

req := NewRequestBuilder().
    Method("POST").
    URL("https://api.example.com").
    Header("Content-Type", "application/json").
    Build()

The Builder pattern is useful when objects require multiple configuration steps, when enforcing a specific construction sequence, for creating immutable objects, or when the construction process needs validation.

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Read sender address
	from, _ := reader.ReadString('\n')
	from = strings.TrimSpace(from)

	// Read number of recipients
	toCountStr, _ := reader.ReadString('\n')
	toCount, _ := strconv.Atoi(strings.TrimSpace(toCountStr))

	// Read each recipient address
	toAddresses := make([]string, toCount)
	for i := 0; i < toCount; i++ {
		addr, _ := reader.ReadString('\n')
		toAddresses[i] = strings.TrimSpace(addr)
	}

	// Read number of CC recipients
	ccCountStr, _ := reader.ReadString('\n')
	ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))

	// Read each CC address
	ccAddresses := make([]string, ccCount)
	for i := 0; i < ccCount; i++ {
		addr, _ := reader.ReadString('\n')
		ccAddresses[i] = strings.TrimSpace(addr)
	}

	// Read subject
	subject, _ := reader.ReadString('\n')
	subject = strings.TrimSpace(subject)

	// Read body
	body, _ := reader.ReadString('\n')
	body = strings.TrimSpace(body)

	// TODO: Use the EmailBuilder with method chaining to construct the email
	// Start with NewEmailBuilder(), then chain From(), To(), CC(), Subject(), Body()
	// Finally call Build() to get the Email

	// TODO: Print the email summary using the Summary() method
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming