建造者模式与原型模式 — 创建型设计模式续篇
建造者模式与原型模式 — 创建型设计模式续篇
一、建造者模式(Builder Pattern)
问题场景
假设你在开发一个 HTTP 请求配置对象,它有十几个可选参数:URL(必填)、Method、Headers、Timeout、Proxy、RetryCount、Body、ContentType、Auth、CachePolicy……如果用传统的结构体初始化,要么写一个参数巨多的构造函数,要么创建一个全是零值的结构体再逐个赋值——前者难读难记,后者容易漏设关键参数。
核心思想
建造者模式把"一步步搭建复杂对象"这件事单独拎出来,交给一个专门的 Builder 来做。Builder 提供一组链式方法,每个方法设置一个属性然后返回 Builder 本身,最后调用 Build() 拿到成品对象。
这个过程就像点外卖选套餐:先选主菜,再选饮品,再选甜点,最后确认下单——你不需要一次性在脑子里记住所有选项,一步一步来就行。
Go 实现:HTTP 请求配置
package main
import "fmt"
// RequestConfig 是我们要构建的复杂对象
type RequestConfig struct {
URL string
Method string
Headers map[string]string
Timeout int // 秒
RetryCount int
ContentType string
AuthToken string
}
// RequestBuilder 负责逐步构建 RequestConfig
type RequestBuilder struct {
config RequestConfig
}
// NewRequestBuilder 创建 Builder,URL 是必填参数
func NewRequestBuilder(url string) *RequestBuilder {
return &RequestBuilder{
config: RequestConfig{
URL: url,
Method: "GET", // 默认值
Headers: map[string]string{},
Timeout: 30, // 默认 30 秒
},
}
}
// Method 设置请求方法,返回 Builder 便于链式调用
func (b *RequestBuilder) Method(method string) *RequestBuilder {
b.config.Method = method
return b
}
// Timeout 设置超时时间
func (b *RequestBuilder) Timeout(seconds int) *RequestBuilder {
b.config.Timeout = seconds
return b
}
// Retry 设置重试次数
func (b *RequestBuilder) Retry(count int) *RequestBuilder {
b.config.RetryCount = count
return b
}
// ContentType 设置内容类型
func (b *RequestBuilder) ContentType(ct string) *RequestBuilder {
b.config.ContentType = ct
return b
}
// Auth 设置认证令牌
func (b *RequestBuilder) Auth(token string) *RequestBuilder {
b.config.AuthToken = token
return b
}
// Header 添加一个请求头
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.config.Headers[key] = value
return b
}
// Build 返回最终的 RequestConfig
func (b *RequestBuilder) Build() *RequestConfig {
return &b.config
}
func main() {
// 链式调用,清晰易读
config := NewRequestBuilder("https://api.example.com/users").
Method("POST").
ContentType("application/json").
Auth("Bearer abc123").
Header("X-Request-Id", "req-001").
Timeout(60).
Retry(3).
Build()
fmt.Printf("请求配置:\n")
fmt.Printf(" URL: %s\n", config.URL)
fmt.Printf(" Method: %s\n", config.Method)
fmt.Printf(" ContentType: %s\n", config.ContentType)
fmt.Printf(" Auth: %s\n", config.AuthToken)
fmt.Printf(" Timeout: %d 秒\n", config.Timeout)
fmt.Printf(" Retry: %d 次\n", config.RetryCount)
fmt.Printf(" Headers: %v\n", config.Headers)
}
运行输出(预期):
请求配置:
URL: https://api.example.com/users
Method: POST
ContentType: application/json
Auth: Bearer abc123
Timeout: 60 秒
Retry: 3 次
Headers: map[X-Request-Id:req-001]
带指挥者(Director)的变体
有些场景中,构建步骤的顺序是固定的。比如组装一台电脑总是"CPU → RAM → GPU → 存储"这个流程,这时候可以引入一个 Director 来编排构建步骤:
// Director 封装固定的构建流程
type ComputerDirector struct{}
func (d *ComputerDirector) BuildHighEnd(b *ComputerBuilder) *Computer {
return b.SetCPU("Intel i9").
SetRAM("64GB DDR5").
SetGPU("RTX 4090").
SetStorage("2TB NVMe SSD").
Build()
}
func (d *ComputerDirector) BuildBudget(b *ComputerBuilder) *Computer {
return b.SetCPU("Intel i3").
SetRAM("8GB DDR4").
SetGPU("Integrated").
SetStorage("256GB SSD").
Build()
}
Director 的好处是:预设方案可以复用,调用方只需说"我要高端配置"或"我要经济配置",不需要逐个参数指定。
什么时候用建造者模式
| 适用 | 不适用 |
|---|---|
| 对象有很多可选参数(>4 个) | 对象只有 2-3 个属性,直接用结构体就行 |
| 参数之间有约束关系(比如设了 Auth 就必须设 ContentType) | 参数都是必填的,用普通构造函数更简单 |
| 需要多种预设配置方案 | 不需要链式调用的场景 |
Go 建造者模式 vs Java 建造者模式
Go 没有类的构造函数重载,也没有 private Builder 内部类。Go 的建造者通常就是:
- 一个结构体(Builder)持有产品字段
- 方法返回
*Builder实现链式调用 Build()返回最终产品
这比 Java 的写法简洁得多——不需要写 inner static class,不需要 Lombok 注解,几个方法就搞定了。
二、原型模式(Prototype Pattern)
问题场景
你需要创建一批结构相同的配置对象,比如 10 个服务器节点配置,它们的主机名、IP 前缀、端口都差不多,只是编号不同。如果一个一个手写,既繁琐又容易出错。
核心思想
原型模式就是先造一个模板对象(原型),然后通过克隆它来批量创建新对象,再在克隆体上微调差异部分。
就像复印文件:你不会重新打字写一份一模一样的合同,而是复印一份然后在复印件上修改细节。
Go 实现:深拷贝与浅拷贝
Go 里克隆对象有两种方式:
- 浅拷贝:只复制值类型字段,引用类型(slice、map、指针)共享底层数据
- 深拷贝:所有字段都独立复制,新对象和老对象完全脱耦
package main
import "fmt"
// ServerConfig 服务器配置(原型)
type ServerConfig struct {
Hostname string
IP string
Port int
Tags []string // 引用类型,浅拷贝会共享
Metadata map[string]string // 引用类型,浅拷贝会共享
}
// Clone 浅拷贝:值类型独立,引用类型共享
func (s *ServerConfig) Clone() *ServerConfig {
copy := *s // Go 的 * 操作做浅拷贝
return ©
}
// DeepClone 深拷贝:所有字段完全独立
func (s *ServerConfig) DeepClone() *ServerConfig {
newTags := make([]string, len(s.Tags))
copy(newTags, s.Tags) // 复制切片
newMeta := make(map[string]string)
for k, v := range s.Metadata {
newMeta[k] = v // 复制 map
}
return &ServerConfig{
Hostname: s.Hostname,
IP: s.IP,
Port: s.Port,
Tags: newTags,
Metadata: newMeta,
}
}
func main() {
// 创建原型
prototype := &ServerConfig{
Hostname: "node-template",
IP: "10.0.0.1",
Port: 8080,
Tags: []string{"web", "backend"},
Metadata: map[string]string{"env": "prod", "region": "us-west"},
}
// 浅拷贝克隆 — 修改 Tags 会影响原型!
shallowClone := prototype.Clone()
shallowClone.Hostname = "node-01" // 值类型:不影响原型
shallowClone.Tags[0] = "frontend" // 引用类型:会影响原型!
fmt.Println("=== 浅拷贝后 ===")
fmt.Printf("原型 Tags: %v\n", prototype.Tags) // 被修改了!
fmt.Printf("克隆 Tags: %v\n", shallowClone.Tags) // 和原型共享
// 深拷贝克隆 — 完全独立,互不影响
deepClone := prototype.DeepClone()
deepClone.Hostname = "node-02"
deepClone.Tags[0] = "database" // 不影响原型
fmt.Println("=== 深拷贝后 ===")
fmt.Printf("原型 Tags: %v\n", prototype.Tags) // 不受影响
fmt.Printf("深克隆 Tags: %v\n", deepClone.Tags) // 完全独立
}
运行输出(预期):
=== 浅拷贝后 ===
原型 Tags: [frontend backend] ← 被修改了!
克隆 Tags: [frontend backend]
=== 深拷贝后 ===
原型 Tags: [frontend backend] ← 不受影响
深克隆 Tags: [database backend]
教训:如果原型对象包含引用类型字段(slice、map、嵌套指针),必须用深拷贝。浅拷贝虽然快,但共享底层数据可能导致意外的副作用。
原型模式的批量使用
package main
import "fmt"
type ServerNode struct {
Hostname string
IP string
Port int
Role string
}
// Clone 深拷贝(这里只有值类型,浅拷贝够用)
func (n *ServerNode) Clone() *ServerNode {
copy := *n
return ©
}
func main() {
// 创建原型模板
template := &ServerNode{
IP: "10.0.1.",
Port: 8080,
Role: "worker",
}
// 用原型克隆 + 微调,批量创建 5 个节点
nodes := make([]*ServerNode, 5)
for i := 0; i < 5; i++ {
node := template.Clone()
node.Hostname = fmt.Sprintf("worker-%d", i+1)
node.IP = fmt.Sprintf("%s%d", template.IP, 100+i)
nodes[i] = node
}
for _, n := range nodes {
fmt.Printf("%s @ %s:%d (%s)\n", n.Hostname, n.IP, n.Port, n.Role)
}
}
运行输出(预期):
worker-1 @ 10.0.1.100:8080 (worker)
worker-2 @ 10.0.1.101:8080 (worker)
worker-3 @ 10.0.1.102:8080 (worker)
worker-4 @ 10.0.1.103:8080 (worker)
worker-5 @ 10.0.1.104:8080 (worker)
什么时候用原型模式
| 适用 | 不适用 |
|---|---|
| 需要批量创建结构相似的对象 | 对象间完全不同,没有共性 |
| 对象初始化代价大(如读数据库、网络请求) | 对象初始化很快,直接新建即可 |
| 避免重复设置相同的默认值 | 需要保证每个对象完全独立(但用深拷贝就行) |
Go 原型模式 vs 其他语言
Go 没有内置的 clone() 方法或 Cloneable 接口。你需要自己实现 Clone() 方法:
- 如果只有值类型字段:
copy := *original(浅拷贝即可) - 如果有引用类型字段:手动
make+copy(深拷贝) - 更复杂的情况:可以用
encoding/json序列化再反序列化(通用深拷贝方案)
// JSON 深拷贝 — 万能方案,但性能较差
func DeepCloneJSON[T any](original T) T {
data, _ := json.Marshal(original)
var clone T
json.Unmarshal(data, &clone)
return clone
}
这种方案通用但慢,适合不频繁克隆的场景。高性能场景还是手写深拷贝。
三、创建型模式总结
至此,创建型设计模式的五种经典模式都学完了:
| 模式 | 核心思想 | Go 实现特点 |
|---|---|---|
| 简单工厂 | 一个函数根据参数返回不同类型 | 返回 interface{} 或具体接口类型 |
| 工厂方法 | 每种产品有自己的工厂 | 用接口+多实现替代继承 |
| 抽象工厂 | 创建一组相关产品的家族 | 接口组合,返回多个产品 |
| 建造者 | 逐步构建复杂对象 | 链式方法 + Build() |
| 原型 | 克隆已有对象 | 手写 Clone()/DeepClone() 或 JSON 序列化 |
Go 和 Java/C++ 的实现差异:
- Go 没有继承,工厂和抽象工厂用接口组合代替
- Go 没有构造函数重载,建造者模式更显价值
- Go 没有内置 clone,原型模式需要自己实现深拷贝

浙公网安备 33010602011771号