策略模式在go中需解决并发安全、状态隔离和切换时机三大问题:接口应窄且稳定,依赖通过构造函数注入,运行时切换须用sync.rwmutex保护,策略实例须无状态或线程安全,注册表应采用编译期类型安全方式。

策略模式在 Go 中不是“加个接口就能跑”,而是必须解决并发安全、状态隔离和切换时机三个实际问题。直接用全局变量赋值或裸指针替换策略,在高并发下大概率 panic。
策略接口定义必须窄且稳定,别塞配置和生命周期方法
接口里只保留核心行为契约,比如 Pay(amount float64) error 或 Sort(data []int) []int。任何跟初始化、销毁、日志、重试相关的逻辑都该从接口里剔除——它们属于工厂或上下文的责任。
- 错误做法:在接口里加
Init()、Close()或SetLogger(*log.Logger),导致所有实现被迫写空方法或重复注入 - 正确路径:依赖通过构造函数传入,如
NewAlipay(client *alipay.Client, logger *log.Logger),返回的是PaymentStrategy接口类型,不是具体 struct - 参数要收敛:避免
Pay(amount float64, currency string, ctx context.Context, timeout time.Duration)这种不断膨胀的签名;统一用 DTO 封装输入,如type PayRequest struct { Amount float64; Currency string }
运行时切换必须带读写锁,不能裸改全局变量
HTTP handler 里直接写 currentStrategy = newVIPStrategy() 是高危操作——多个请求同时写,结果不可预测。策略切换不是“换引用”那么简单,它涉及旧策略是否还在执行、新策略是否已就绪。
- 用
sync.RWMutex包裹策略字段,读多写少场景下比sync.Mutex更高效 - 切换动作应放在配置热重载入口(如监听 etcd 变更)、启动初始化阶段,而非高频请求路径中频繁调用
SetStrategy() - 如果策略切换极低频(如服务启动后不再变),用
sync.Once初始化即可,无需锁 - 绝对不要在 handler 内部做策略赋值;把策略实例封装进 service 结构体,由 service 统一管理
策略实例必须无状态或线程安全,别共享可变字段
一个 *RateLimiter 实例被多个 goroutine 并发调用时,如果内部用了未加锁的 map 或普通 int 计数器,必然数据错乱。策略不是“算法代码”就够了,它得是“能并发跑的单元”。
- 首选无状态设计:策略只读取传入参数 + 预加载的只读规则表(如 JSON 配置解析后存为
sync.Map或切片) - 若必须维护状态(如滑动窗口限流器),确保每个策略实例独占一份,不复用;别让两个 HTTP 请求共用同一个
*SlidingWindow - 避免在策略方法内 new 全局单例或调用非线程安全第三方库(如某些老版本 redis 客户端的 conn 池)
- 测试时用手工 mock,验证行为选择逻辑,而不是 mock 网络调用——后者掩盖了状态污染问题
注册表要用编译期变量,别用字符串映射查策略
用 map[string]interface{} 存策略再靠类型断言取出,等于放弃 Go 的类型安全。运行时报 panic: interface conversion: interface {} is *alipayImpl, not PaymentStrategy 是常见翻车点。
- 用包级变量注册:如
var Strategies = map[string]PaymentStrategy{ "alipay": NewAlipay(...), "wechat": NewWechat(...), },键是字符串但值是确定接口类型 - 更推荐编译期注册:定义
func RegisterStrategy(name string, s PaymentStrategy),在各策略文件init()函数里注册,避免字符串硬编码 - 动态路由别写满屏 if-else:按订单类型、用户等级等条件选策略时,用结构化匹配(如
switch order.Type)或规则引擎预编译,而不是 runtime.Eval - 函数类型可替代简单策略:如排序规则只需
type CompareFunc func(a, b int) bool,比定义接口+struct 更轻量
真正难的不是写几个 struct 实现接口,而是让策略在 Goroutine 并发调用下不打架、不污染、不 panic——这要求你从构造函数开始就盯紧状态边界,而不是等线上报警才去查竞态。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











