Go策略模式应通过接口+构造函数注入实现,接口只声明算法行为如Pay(),避免Init/Config等方法;策略结构体需在构造函数中校验依赖;运行时切换须用sync.RWMutex保护字段,禁用全局变量;无状态逻辑优先用函数类型。

Go 里策略模式不是靠继承,而是靠接口 + 构造函数注入 + 字段赋值来实现的;运行时切换必须用 sync.RWMutex 保护,不能直接改全局变量。
怎么定义策略接口才不踩坑
接口只该声明「算法行为」,不该掺杂依赖或生命周期方法。比如支付策略,Pay(amount float64) error 就够了,别加 Init()、SetLogger() 或 Config() *Config 这类方法。
- 接口名用名词或形容词,如
PaymentStrategy,别写IPaymentStrategy(Go 不用 I 前缀) - 方法必须导出(首字母大写),否则别的包没法实现
- 别为了“以后可能加”提前塞方法——等真要支持新策略时再扩接口,Go 崇尚小接口
- 别用
interface{}当策略类型,它没有编译期校验,调用Execute()会 panic
具体策略怎么初始化才安全
策略结构体不是裸 struct,它往往要带 client、logger、配置等依赖。这些必须在构造函数里传入,并做非空检查,不能留到 Pay() 里才初始化。
- 用工厂函数,比如
NewAlipay(client *alipay.Client, logger *log.Logger),而不是暴露字段让调用方自己 new - 构造函数里校验关键依赖是否为 nil,比如
if client == nil { panic("client is required") } - 别把 HTTP client、DB conn 这类共享资源设成全局变量——并发下会错乱或泄漏
- 避免在策略方法里做 heavy 初始化(如读文件、解析 YAML),首次调用慢且不可控
type WechatPayStrategy struct {
client *http.Client
appID string
}
func NewWechatPayStrategy(client *http.Client, appID string) *WechatPayStrategy {
if client == nil {
panic("client is required")
}
return &WechatPayStrategy{client: client, appID: appID}
}
运行时切换策略为什么不能用全局变量
用 var currentStrategy PaymentStrategy + SetStrategy() 看似简单,但多个 goroutine 同时写就会 panic:并发写未加锁的变量是 Go runtime 明确禁止的。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- HTTP handler 里改全局策略,一个请求改了,所有后续请求都受影响
- 正确做法是把策略作为服务结构体的字段,用
sync.RWMutex保护读写 - 读多写少场景下,
RWMutex比Mutex性能更好 - 如果策略启动后就固定(比如从配置加载一次),用
sync.Once初始化即可,不用锁
type PaymentService struct {
mu sync.RWMutex
stra PaymentStrategy
}
func (p *PaymentService) SetStrategy(s PaymentStrategy) {
p.mu.Lock()
defer p.mu.Unlock()
p.stra = s
}
func (p *PaymentService) Process(amount float64) error {
p.mu.RLock()
defer p.mu.RUnlock()
return p.stra.Pay(amount)
}
什么时候该用函数类型代替接口
纯无状态算法(比如格式化字符串、计算折扣率、简单校验逻辑),优先用 type LogFormatter func(string, map[string]interface{}) string 这种函数类型,比写空结构体+方法更轻量。
- 函数类型调用无开销,测试时直接传闭包,mock 更容易
- 需要维护状态(比如限流器要记时间戳、重试器要存计数器)才用结构体 + 方法
- 别为了“统一风格”硬把所有策略都写成结构体——那是过度设计
- 函数类型无法携带字段,所以不能替代有依赖的策略(比如带
*http.Client的支付策略)
最常被忽略的一点:策略注册表不是可选功能,而是真实业务的刚需。订单类型、用户等级、地区这些条件决定用哪个策略,硬编码 if order.Type == "vip" 很快失控。得用 map[string]PaymentStrategy 或更稳的 map[string]func() PaymentStrategy 配合读写锁来做动态路由——这部分没封装好,策略模式就只是个玩具。

















