Menu
Coddy logo textTech

Go에서의 빌더 패턴

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 80번째.

빌더 패턴(Builder Pattern)은 복잡한 객체의 생성 과정과 표현 방법을 분리합니다. Functional Options 패턴과 달리, 빌더는 객체를 단계별로 설정하는 메서드를 가진 전용 빌더 구조체(builder struct)를 사용합니다.

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
}

각 메서드는 빌더 포인터를 반환하여 메서드 체이닝을 가능하게 합니다. Build() 메서드는 구성을 마무리하고 생성된 객체를 반환합니다:

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

빌더 패턴은 객체에 여러 설정 단계가 필요하거나 특정 생성 순서를 강제하고 싶을 때 탁월합니다. 특히 불변 객체를 생성하거나 생성 프로세스 자체에 검증이 필요한 경우에 유용합니다.

challenge icon

챌린지

쉬움

빌더 패턴(Builder Pattern)을 사용하여 이메일 메시지 작성기를 만들어 봅시다! 이메일 메시지는 수신자, 참조(CC), 제목, 본문, 첨부 파일 등 많은 선택적 구성 요소를 가지고 있어, 메서드 체이닝을 통한 단계별 구축에 아주 적합한 후보입니다.

코드는 다음 두 개의 파일로 구성합니다:

  • email.go: 이메일 구조체와 해당 빌더를 정의합니다.

    다음 필드들을 가진 Email 구조체를 생성하세요:

    • from (string)
    • to (문자열 슬라이스)
    • cc (문자열 슬라이스)
    • subject (string)
    • body (string)

    생성 중인 Email에 대한 포인터를 유지하는 EmailBuilder 구조체를 생성하세요. 초기화된 Email(tocc에 대한 빈 슬라이스 포함)과 함께 새 빌더를 반환하는 NewEmailBuilder()를 구현하세요.

    체이닝을 위해 각각 *EmailBuilder를 반환하는 다음 빌더 메서드들을 추가하세요:

    • From(address string) - 발신자 설정
    • To(address string) - to 슬라이스에 수신자 추가
    • CC(address string) - cc 슬라이스에 참조 수신자 추가
    • Subject(subject string) - 제목 설정
    • Body(body string) - 이메일 본문 설정

    구축된 *Email을 반환하는 Build() 메서드를 추가하세요.

    마지막으로, Email에 다음 형식의 문자열을 반환하는 Summary() 메서드를 추가하세요:

    From: [from]
    To: [comma-separated to addresses]
    CC: [comma-separated cc addresses or "none"]
    Subject: [subject]
    Body: [body]
  • main.go: 빌더를 사용하여 이메일 메시지를 구축하고 표시합니다.

    발신자 주소를 읽은 다음, 수신자 수와 각 수신자 주소를 읽습니다. 그다음, 참조 수신자 수와 각 참조 주소를 읽습니다. 마지막으로 제목과 본문을 읽습니다.

    메서드 체이닝과 함께 EmailBuilder를 사용하여 이메일을 구축한 다음, 그 요약(summary)을 출력하세요.

다음 입력들이 제공됩니다:

  • 1행: 발신자 주소
  • 2행: 수신자 수
  • 다음 행들: 각 수신자 주소
  • 다음 행: 참조 수신자 수
  • 다음 행들: 각 참조 주소
  • 다음 행: 제목
  • 마지막 행: 본문

예를 들어, 다음과 같이 입력되면:

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

출력은 다음과 같아야 합니다:

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.

그리고 다음과 같이 입력되면:

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

출력은 다음과 같아야 합니다:

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

빌더를 사용하면 반복적인 To() 호출을 통해 여러 수신자를 한 번에 하나씩 추가할 수 있으며, 이 패턴이 많은 선택적 필드가 있는 경우에도 구축 과정을 얼마나 깔끔하고 가독성 있게 유지하는지 확인해 보세요.

치트 시트

빌더 패턴(Builder Pattern)은 복잡한 객체의 생성 과정을 표현 방식과 분리하며, 객체를 단계별로 설정하는 메서드를 가진 전용 빌더 구조체를 사용합니다.

기본 구조:

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

빌더 메서드는 메서드 체이닝(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
}

Build() 메서드는 구성을 마무리하고 생성된 객체를 반환합니다:

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

메서드 체이닝을 사용한 예시:

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

빌더 패턴은 객체에 여러 설정 단계가 필요할 때, 특정 생성 순서를 강제해야 할 때, 불변(immutable) 객체를 생성할 때, 또는 생성 과정에서 검증(validation)이 필요할 때 유용합니다.

직접 해보기

package main

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

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

	// 발신자 주소 읽기
	from, _ := reader.ReadString('\n')
	from = strings.TrimSpace(from)

	// 수신자 수 읽기
	toCountStr, _ := reader.ReadString('\n')
	toCount, _ := strconv.Atoi(strings.TrimSpace(toCountStr))

	// 각 수신자 주소 읽기
	toAddresses := make([]string, toCount)
	for i := 0; i < toCount; i++ {
		addr, _ := reader.ReadString('\n')
		toAddresses[i] = strings.TrimSpace(addr)
	}

	// 참조(CC) 수신자 수 읽기
	ccCountStr, _ := reader.ReadString('\n')
	ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))

	// 각 참조(CC) 주소 읽기
	ccAddresses := make([]string, ccCount)
	for i := 0; i < ccCount; i++ {
		addr, _ := reader.ReadString('\n')
		ccAddresses[i] = strings.TrimSpace(addr)
	}

	// 제목 읽기
	subject, _ := reader.ReadString('\n')
	subject = strings.TrimSpace(subject)

	// 본문 읽기
	body, _ := reader.ReadString('\n')
	body = strings.TrimSpace(body)

	// TODO: 메서드 체이닝을 사용하여 이메일을 구성하도록 EmailBuilder를 사용하세요
	// NewEmailBuilder()로 시작하여 From(), To(), CC(), Subject(), Body()를 차례로 연결하세요
	// 마지막으로 Build()를 호출하여 Email을 얻으세요

	// TODO: Summary() 메서드를 사용하여 이메일 요약을 출력하세요
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨