目录
Please enable Javascript to view the contents

Go-设计模式

 ·  ☕ 3 分钟

重要

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 替代,优先用并发原语。

参考链接

分享

Hex
作者
Hex
CloudNative Developer