Go中多数设计模式非必需,真正高频有用的是Worker Pool、Dependency Injection和Option Pattern;其余常被函数、接口或channel简化替代。
大多数 Go 项目不需要刻意套用经典设计模式。Go 的接口隐式实现、函数是一等公民、组合优先于继承等特性,天然消解了大量模式的“必要性”。比如 io.Reader 和 io.Writer 就是策略模式的极简落地——你不用定义 ReadStrategy 接口,直接用 func([]byte) (int, error) 或实现 Read 方法就行。
硬套 Singleton 容易写出带锁的全局变量,Factory Method 常被一个 func NewXxx(...) *Xxx 函数替代,Observer 往往退化为 channel + goroutine 的简单通知。
真正高频、有明确价值的是三类:与并发强相关的(如 Worker Pool)、与依赖解耦强相关的(如 Dependency Injection)、以及封装复杂初始化逻辑的(如 Option Pattern)。
Worker Pool:避免无节制启动 goroutine 导致内存暴涨或调度压力,典型结构是 chan job + 固定数量的 for range jobs 循环Dependency Injection:不是用框架,而是靠构造函数参数显式传入依赖,例如 type Service struct { db *sql.DB; cache *redis.Client },再通过 NewService(db, cache) 初始化Option Pattern:解决可选参数爆炸问题,比用 struct 字段赋零值更清晰,也比多个重载函数更 Go 风格。核心是定义 type Option func(*Config) 类型,再提供 WithTimeout(...) 等函数常见错误是把 Option 写成闭包却忽略并发安全,或者在应用选项时顺序错乱导致后设覆盖前设(比如 WithTimeout 被 WithDefaults 覆盖)。正确做法是让每个 Option 只改自己负责的字段,并在 NewXxx 中按调用顺序依次 apply。
type Config struct {
Timeout time.Duration
Retries int
}
type Option func(*Config)
func WithTimeout(d time.Duration) Option {
return func(c *Config) {
c.Timeout = d
}
}
func WithRetries(n int) Option {
return func(c *Config) {
c.Retries = n
}
}
func NewClient(opts ...Option) Client {
cfg := &Config{Timeout: 5 time.Second, Retries: 3}
for _, opt := range opts {
opt(cfg)
}
return &Client{cfg: cfg}
}
注意:不要在 WithTimeout 里做 time.AfterFunc 这类副作用操作——Option 应该只配置,不执行。
比起记住怎么写 Decorator,更关键的是识别何时不该用。比如用 interface{} 做泛型容器、在 HTTP handler 里直接调用 log.Fatal、把 config 结构体暴露给所有包导致修改牵一发而动全身——这些才是实际项目中最常拖垮迭代
速度的问题。
设计模式的价值不在“用了多少”,而在“有没有能力判断某个场景下该删掉什么、合并什么、推迟抽象到哪一步”。Go 的简洁性恰恰要求你对抽象的时机更敏感,而不是更早。