策略接口应优先用泛型(Go 1.18+),如 Strategy[T any];无状态策略可值接收,有状态必须指针接收;运行时切换需 sync.RWMutex 保护;注册表宜封装而非裸 map。

策略接口定义必须用 interface{} 还是具体类型?
Go 没有泛型约束前(Go 1.18 之前),策略接口通常用 interface{} 接收任意输入,但这会丢失类型安全、增加运行时断言风险。Go 1.18+ 强烈建议用泛型接口,避免类型转换错误。
- 旧写法:
type Strategy interface { Execute(data interface{}) error }→ 调用方需手动data.(string),易 panic - 推荐写法:
type Strategy[T any] interface { Execute(data T) error }→ 编译期检查输入类型 - 若策略只处理一种固定类型(如
float64),直接定义为type DiscountStrategy interface { Apply(price float64) float64 }更清晰
Context 结构体要不要持有策略实例的指针?
要,但取决于策略是否带状态。无状态策略(如纯函数式计算)可传值;有状态策略(如带计数器、缓存)必须传指针,否则每次调用都是副本,状态不累积。
type Context struct {
strategy Strategy[float64] // 接口类型,值接收即可
}
// ✅ 正确:策略实现本身无内部字段,或字段不需跨调用共享
type FlatRateDiscount struct{}
func (f FlatRateDiscount) Apply(price float64) float64 { return price * 0.9 }
// ❌ 错误:若策略含计数器,用值接收会导致每次调用都重置 count
type CountedDiscount struct {
count int
}
func (c CountedDiscount) Apply(price float64) float64 { // 值接收 → c 是副本
c.count++ // 不影响原实例
return price 0.95
}
// ✅ 应改为指针接收:func (c CountedDiscount) Apply(price float64) float64
如何在运行时安全切换策略而不引发竞态?
Context 中的策略字段若被多 goroutine 并发读写(如管理员动态更新折扣策略),必须加锁。不要依赖“只读切换”假设 —— Go 编译器不保证字段读写的原子性。
- 用
sync.RWMutex:读多写少场景下,RLock()供Execute()使用,Lock()仅用于SetStrategy() - 避免在策略方法内调用
Context.SetStrategy(),容易死锁 - 切换后可加简单校验,比如调用
strategy.(fmt.Stringer).String()确保非 nil
type Context struct {
mu sync.RWMutex
strategy Strategy[float64]
}
func (c *Context) Execute(price float64) float64 {
c.mu.RLock()
defer c.mu.RUnlock()
if c.strategy == nil {
return price // 或 panic / 返回错误
}
return c.strategy.Apply(price)
}
func (c *Context) SetStrategy(s Strategy[float64]) {
c.mu.Lock()
defer c.mu.Unlock()
c.strategy = s
}
为什么不用 map[string]Strategy 直接做策略注册表?
可以,但要注意 key 冲突、类型擦除和初始化顺序问题。纯 map 注册缺少编译期校验,运行时取错 key 会 panic;更健壮的做法是封装成带方法的注册器。
立即学习“go语言免费学习笔记(深入)”;
- 别直接写:
strategies := map[string]Strategy[float64]{ "vip": VIPDiscount{} }→ key 拼错无提示 - 推荐封装:
reg := NewStrategyRegistry(); reg.Register("vip", VIPDiscount{}),内部用 map + 类型断言 + 重复注册检测 - 若策略需依赖注入(如 DB client),map[string]interface{} + reflect 调用太重,应改用函数工厂:
func() Strategy[float64] { return &DBAwareDiscount{db: db} }
策略切换本身不复杂,真正容易出问题的是状态共享边界和并发控制粒度 —— 多数线上 bug 都来自策略实例被意外复用或未加锁覆盖。










