ការបង្កើត កម្រិតស្មុគស្មាញ: មធ្យម

Builder in Go

Separate a multi-step construction workflow from the final representation so the same input can produce different outputs cleanly.

The Problem

Some values are assembled through a sequence of steps, not one constructor call. When those steps live inline in handlers, the workflow becomes repetitive and the output format becomes hard to change. The same order summary may need to produce a customer-facing confirmation, an ops checklist, or an internal export.

The Solution

Builder pulls the construction steps into a dedicated object while a director coordinates the sequence. In Go, the pattern often appears as a focused interface with methods that add sections, totals, or metadata. The builder accumulates state, and the caller decides which concrete builder to use.

Structure

Builder Pattern
Step 1 of 6

The Client

The client selects which concrete builder to use and hands it to the director. No construction logic lives here — it's purely a wiring point. Swap MarkdownSummaryBuilder for another builder without touching the director.

  • Product: CheckoutSummary is the value being assembled.
  • Builder interface: Declares the steps needed to produce the summary.
  • Concrete builder: MarkdownSummaryBuilder stores output in a markdown-friendly form.
  • Director: CheckoutSummaryDirector runs the construction steps in a fixed order.
  • Client: Chooses the builder and asks the director to assemble the result.

Implementation

This example turns an order into a formatted checkout summary. The director always performs the same steps, while the builder owns how the final representation is assembled and rendered.

package main

import (
	"fmt"
	"strings"
)

type LineItem struct {
	Name      string
	Qty       int
	UnitPrice int
}

type Order struct {
	ID       string
	Customer string
	Items    []LineItem
}

func (o Order) Total() int {
	total := 0
	for _, item := range o.Items {
		total += item.Qty * item.UnitPrice
	}
	return total
}

type CheckoutSummary struct {
	Title  string
	Body   []string
	Footer string
}

func (s CheckoutSummary) Render() string {
	parts := []string{s.Title}
	parts = append(parts, s.Body...)
	parts = append(parts, s.Footer)
	return strings.Join(parts, "\n")
}

func formatMoney(cents int) string {
	return fmt.Sprintf("$%.2f", float64(cents)/100)
}

Real-World Analogy

Think of a kitchen assembling the same order into different outputs. One workflow collects the same ingredients and steps, but one builder plates a dine-in meal while another packs a delivery box with labels and receipts.

Pros and Cons

ProsCons
Makes multi-step construction explicit and easier to reuse.Introduces more types and ceremony than a plain constructor.
Supports different final representations from the same assembly process.A director can feel unnecessary when the step order is already obvious.
Keeps complex setup logic out of handlers and services.Easy to overuse for simple structs with ordinary field assignment.

Best Practices

  • Use Builder when construction has meaningful steps or optional sections, not just many fields.
  • Keep the director optional. In Go, direct builder calls are fine when the sequence is already obvious.
  • Prefer explicit step names over generic SetField methods so the workflow stays readable.
  • Return a final immutable value from Build rather than exposing the builder internals.
  • Do not force Builder where functional options or a plain constructor already solve the problem more simply.

When to Use

  • Construction involves multiple ordered steps or reusable recipes.
  • The same input flow may produce different final representations.
  • You want to keep assembly logic out of transport handlers and services.

When NOT to Use

  • A constructor or struct literal is already clear enough.
  • The build steps add ceremony without capturing real workflow.
  • Only field assignment changes and there is no reusable assembly process.