必须用 sync.rwmutex 保护策略字段读写,setstrategy 需校验 nil 并显式 close 旧策略,读多写少用 rwmutex;纯计算策略优先用函数类型;避免字符串映射查表,改用编译期变量或带缓存的工厂函数。

策略字段必须用 sync.RWMutex 保护读写
直接给结构体字段赋新策略值,不加同步,在并发下必然出错。现象是部分请求走旧逻辑、部分走新逻辑,甚至 panic: invalid memory address —— 因为旧策略被置 nil 后,另一 goroutine 还在调它的方法。
正确做法是把策略作为结构体字段,并用 sync.RWMutex 控制访问:
type PaymentService struct {
mu sync.RWMutex
stra PaymentStrategy
}
func (p *PaymentService) SetStrategy(s PaymentStrategy) {
if s == nil {
panic("strategy cannot be nil")
}
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)
}
- 读多写少场景下,
RWMutex比Mutex更轻量 -
SetStrategy必须校验s == nil,否则后续Pay()调用直接 panic - 不要在 HTTP handler 里调
SetStrategy—— 一个请求改了,所有后续请求都受影响
简单逻辑优先用函数类型,别硬套 interface
如果策略只是纯计算、无状态、不依赖外部资源(如日志、HTTP 客户端、DB 连接),用函数类型最干净:
type SortFunc func([]int) []int
type Sorter struct {
strategy SortFunc
}
func (s *Sorter) Sort(data []int) []int {
return s.strategy(data)
}
// 切换只需赋值:sorter.strategy = sort.Ints
- 无需锁:函数值赋值是原子的
- 测试友好:直接传入闭包或测试函数,不用 mock 接口
- 但无法携带字段 —— 一旦需要超时、重试、配置,就得退回到 struct + interface 方案
策略注册不能靠字符串映射查表
用 map[string]PaymentStrategy 存策略再通过字符串名查找,看似灵活,实则埋雷:高并发下 map 读写需加锁;字符串拼错导致运行时 panic;IDE 无法跳转、重构困难。
更稳妥的做法是编译期确定的变量引用或工厂函数缓存:
// ✅ 推荐:全局变量 + 工厂函数
var (
AlipayStrategy PaymentStrategy = NewAlipay("app123", "key...")
WechatStrategy PaymentStrategy = NewWechat("mch456", "cert...")
)
// ✅ 或带缓存的工厂(避免重复构造)
var strategyCache sync.Map // key: string, value: PaymentStrategy
func GetStrategy(name string) PaymentStrategy {
if v, ok := strategyCache.Load(name); ok {
return v.(PaymentStrategy)
}
s := newStrategyByName(name) // 内部用 switch 构造
strategyCache.Store(name, s)
return s
}
- 字符串查找只应出现在配置解析入口,而非高频执行路径
- 若真要用字符串选型,务必预热缓存,避免每次调用都反射或 map 查找
- 绝对不要用
interface{}+reflect实现“通用策略调度”——调试难、性能差、IDE 失效
切换前必须显式处理旧策略生命周期
策略不是纯函数,它可能持有连接池、缓存、计时器等资源。直接替换而不 Close,会导致泄漏或状态残留。
例如一个 DB 策略持有 *sql.DB,切换时旧实例被丢弃,但连接池未关闭:
- 策略类型应实现
Close() error方法(注意:这不是策略接口的一部分,而是具体实现的责任) -
SetStrategy中应先调旧策略的Close(),再赋新值 - 若旧策略正在处理请求,需决定是否等待完成 —— 这不是锁能解决的,得靠上下文取消或 graceful shutdown 机制
最容易被忽略的一点:策略切换不是原子操作,它涉及“停旧”和“启新”两个阶段,中间存在窗口期。这个窗口的行为(拒绝新请求?继续处理旧请求?)必须由业务逻辑明确定义,不能交给锁或赋值语句隐式决定。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











