Menu
CoddyTech

Go 中的构建器模式

本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 80 课,共 107 课。

构建器模式将复杂对象的构建过程与其表示分离。与函数式选项模式不同,构建器模式使用专用的构建器结构体,通过方法逐步配置对象。

下面介绍如何为 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

挑战

简单

让我们使用建造者模式来构建电子邮件!电子邮件有许多可选部分,例如收件人、抄送、主题、正文和附件,因此非常适合通过方法链逐步构建。

你会将代码分成两个文件:

  • email.go:定义电子邮件结构体及其建造者。

    创建一个 Email 结构体,包含以下字段:

    • from(字符串)
    • to(字符串切片)
    • cc(字符串切片)
    • subject(字符串)
    • body(字符串)

    创建一个 EmailBuilder 结构体,用于保存正在构建的 Email 的指针。实现 NewEmailBuilder(),返回一个新的建造者,其中包含已初始化的 Email(to 和 cc 为空切片)。

    添加以下建造者方法,每个方法都返回 *EmailBuilder,以便进行链式调用:

    • From(address string):设置发件人
    • To(address string):将收件人添加到 to 切片
    • CC(address string):将收件人添加到 cc 切片
    • Subject(subject string):设置主题
    • Body(body string):设置电子邮件正文

    添加一个 Build() 方法,返回构建好的 *Email。

    最后,在 Email 上添加一个 Summary() 方法,以以下格式返回字符串:

    From: [from]
    To: [comma-separated to addresses]
    CC: [comma-separated cc addresses or "none"]
    Subject: [subject]
    Body: [body]
  • main.go:使用建造者构建并显示电子邮件。

    读取发件人地址,然后读取收件人数量和每个收件人的地址。接着读取抄送收件人数量和每个抄送收件人的地址。最后读取主题和正文。

    使用 EmailBuilder 进行方法链式调用来构建电子邮件,然后打印其摘要。

输入内容如下:

  • 第 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() 一次添加一个收件人;即使有许多可选字段,这种模式也能让构建过程保持清晰易读。

亲手试试

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

	// 读取抄送收件人数量
	ccCountStr, _ := reader.ReadString('\n')
	ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))

	// 读取每个抄送收件人的地址
	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自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Go 在线编译器