重要
Go 没有继承和重载——设计模式的实现方式与其他语言不同。Go 靠接口 + 组合 + 闭包 + 并发原语实现模式,而非继承树。
最常用的三个:Functional Options(构造器)、Strategy(接口切换算法)、Decorator(Middleware/Reader 包装)。
1. Functional Options 模式(★★★★★)
Go 没有构造函数重载和默认参数。一个包含多个可选配置的结构体,用 Options 模式解决:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| type Server struct {
addr string
timeout time.Duration
maxConn int
tls *tls.Config
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}
func WithMaxConn(n int) Option {
return func(s *Server) { s.maxConn = n }
}
func WithTLS(cfg *tls.Config) Option {
return func(s *Server) { s.tls = cfg }
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
addr: addr,
timeout: 30 * time.Second, // 默认值
maxConn: 100,
}
for _, opt := range opts {
opt(s)
}
return s
}
// 使用
s := NewServer(":8080",
WithTimeout(60*time.Second),
WithMaxConn(1000),
)
|
这是 Go 中最重要的"构造器模式"。gRPC、K8S client-go 等几乎所有 Go 库都在用。
2. 单例模式(sync.Once)
Go 中单例的最简单也是最正确的方式:
1
2
3
4
5
6
7
8
9
10
11
| var (
instance *DB
once sync.Once
)
func GetDB() *DB {
once.Do(func() {
instance = &DB{ /* 初始化 */ }
})
return instance
}
|
sync.Once 保证初始化函数只执行一次,且所有调用者在初始化完成前阻塞。线程安全,零外部锁。
3. 策略模式(接口切换)
定义算法族,通过接口让调用方在运行时选择具体实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // 策略接口
type PaymentStrategy interface {
Pay(amount float64) error
}
type Alipay struct{}
func (a Alipay) Pay(amount float64) error {
fmt.Printf("支付宝支付: %.2f\n", amount)
return nil
}
type Wechat struct{}
func (w Wechat) Pay(amount float64) error {
fmt.Printf("微信支付: %.2f\n", amount)
return nil
}
// 策略使用者
func Checkout(strategy PaymentStrategy, amount float64) error {
return strategy.Pay(amount)
}
|
Go 不需要策略"管理类"——直接传接口值即可,运行时选择。
4. 装饰器模式(Middleware 链)
Go 的装饰器最常见的形态是 HTTP Middleware 和 io.Reader 包装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // HTTP Middleware
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") == "" {
http.Error(w, "Unauthorized", 401)
return
}
next.ServeHTTP(w, r)
})
}
// 链式包装
handler := Logging(Auth(myHandler))
|
io.Reader 包装
1
2
3
4
5
6
7
8
9
10
11
| // 读取时计数的 Reader
type CountingReader struct {
r io.Reader
count int64
}
func (c *CountingReader) Read(p []byte) (int, error) {
n, err := c.r.Read(p)
c.count += int64(n)
return n, err
}
|
5. 工厂模式
Go 的工厂就是返回接口(或具体类型)的普通函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| type Cache interface {
Get(key string) ([]byte, error)
Set(key string, val []byte) error
}
func NewCache(kind string) (Cache, error) {
switch kind {
case "redis":
return newRedisCache()
case "memory":
return newMemoryCache()
default:
return nil, fmt.Errorf("unknown cache: %s", kind)
}
}
|
Go 的工厂不需要抽象工厂接口——一个 switch + 返回接口值足够。
6. 观察者模式(Channel 版)
传统观察者用回调列表,Go 可以用 channel 直接替代:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| type EventBus struct {
subscribers map[string][]chan interface{}
mu sync.RWMutex
}
func (b *EventBus) Subscribe(event string) <-chan interface{} {
b.mu.Lock()
defer b.mu.Unlock()
ch := make(chan interface{}, 10)
b.subscribers[event] = append(b.subscribers[event], ch)
return ch
}
func (b *EventBus) Publish(event string, data interface{}) {
b.mu.RLock()
defer b.mu.RUnlock()
for _, ch := range b.subscribers[event] {
select {
case ch <- data:
default: // 缓冲区满,丢弃或另做处理
}
}
}
|
Channel 天然具备通知和分发能力。带缓冲的 channel 避免发布方阻塞。
7. 适配器模式
让不兼容的接口通过适配器协作——Go 中就是实现一个接口来包装另一个类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // 旧接口
type LegacyPrinter interface {
Print(s string)
}
// 新接口
type ModernPrinter interface {
PrintStructured(data map[string]string) error
}
// 适配器:让 LegacyPrinter 满足 ModernPrinter
type PrinterAdapter struct {
legacy LegacyPrinter
}
func (a PrinterAdapter) PrintStructured(data map[string]string) error {
s := data["content"]
a.legacy.Print(s)
return nil
}
|
8. 总结
| 模式 | Go 实现方式 | 关键机制 |
|---|
| Functional Options | 可变参数 ...Option + 闭包 | 无默认参数时替代构造函数重载 |
| 单例 | sync.Once | 线程安全的一次性初始化 |
| 策略 | 传接口值 | 运行时选择算法,无需管理类 |
| 装饰器 | http.Handler 包装、io.Reader 链 | 函数签名一致时层层嵌套 |
| 工厂 | func NewXxx() Interface | 返回接口,内部 switch |
| 观察者 | Channel | 天然具备通知能力,取代回调列表 |
| 适配器 | 实现目标接口,包装旧类型 | 不修改旧代码 |
Go 设计模式的核心原则:尽量不要引入额外的结构体和抽象层。 如果一个接口只有一个方法,直接传函数更简单;如果一个模式可以用 channel + goroutine 替代,优先用并发原语。
参考链接