Go 中选项模式、单例与策略模式怎么落地?
一句话结论:Go 里谈设计模式要“少谈继承、多用组合与函数”:选项模式(functional options)用“函数即配置”做出可扩展的构造器;单例用 sync.Once 保证线程安全的懒初始化;策略模式用接口抽象行为、运行时替换实现。三者的共同价值是让代码可测、可扩展、少改调用方。
选项模式
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} // 默认值
for _, opt := range opts { // 按序应用
opt(&cfg)
}
return &Client{cfg: cfg}
}
c := NewClient(WithTimeout(time.Second), WithRetries(3))
优点:加配置项不改函数签名、调用方可读性好、天然支持默认值——比一长串参数和“每加一个字段就改构造器”更易维护。
单例模式
var (
once sync.Once
client *Client
)
func GetClient() *Client {
once.Do(func() {
client = NewClient() // 只执行一次,且线程安全
})
return client
}
- sync.Once 保证并发下只初始化一次,比双检锁简洁得多。
- 注意单例是全局可变状态,测试难隔离;必要时提供显式注入或测试替身入口。
- 简单场景也可用包级 var 直接初始化,代价是进程启动即创建。
策略模式
type Notifier interface {
Notify(to, msg string) error
}
type EmailNotifier struct{}
func (EmailNotifier) Notify(to, msg string) error { return nil }
type SmsNotifier struct{}
func (SmsNotifier) Notify(to, msg string) error { return nil }
func Send(n Notifier, to, msg string) error {
return n.Notify(to, msg) // 换 Email/Sms 都行,调用方不变
}
策略模式在 Go 里几乎等于“接口加注入”,常配合注册表 map[string]Notifier 消除大段 switch。
常见追问 / 记忆点
- 追问:什么时候不该用接口/策略?——只有一个实现且短期不变时,先用具体类型,避免过度设计。
- 追问:单例与 sync.Once 还会用在哪?——注册表、默认编码器、一次性资源初始化等。
- 记忆点:选项 = 函数改配置;单例 = Once;策略 = 接口注入;模式是手段,可测可维护才是目的。