go中strategy接口应窄且仅定义行为,依赖通过构造函数注入,运行时切换需加锁,简单逻辑优先用函数类型。

Go 语言里用 Strategy 接口封装算法,不是为了套设计模式,而是为了解决「同一入口、多路逻辑、运行时可换」这个真实问题。核心就一条:接口只管「做什么」,不掺和「怎么做」和「怎么初始化」。
Strategy 接口必须窄且导出
接口方法名必须首字母大写(否则其他包无法实现),参数和返回值越简单越好。别塞 ctx context.Context、logger *log.Logger 这类依赖进去——那是构造函数该干的事。
-
Calculate(amount float64) float64✅ 清晰、无副作用、可测试 -
Calculate(ctx context.Context, amount float64, cfg Config) (float64, error)❌ 把策略契约变成了配置搬运工 - 接口里定义
Init()或Close()方法 ❌ 生命周期管理不属于策略本身
具体策略要靠构造函数注入依赖
每个策略实现体(比如 *Alipay、*WechatPay)应该是一个普通结构体,所有外部依赖(HTTP client、DB、logger)都在 NewXXX() 里传入并保存为字段。这样策略实例才是可复用、可测试、线程安全的。
- 共享状态(如计数器、缓存 map)必须自己加锁或用
atomic,不能指望调用方保护 - 避免空指针 panic:构造函数里对必要依赖做非空校验,比如
if client == nil { panic("client is required") } - 不要用 setter 方法延迟注入——那会让策略处于「半初始化」状态,难以追踪
运行时切换必须带读写锁,别碰全局变量
直接改 var current Strategy 是并发雷区。真实服务里,策略是上下文的一部分,得封装在结构体里,用 sync.RWMutex 控制读写。
-
SetStrategy(s Strategy)必须加mu.Lock(),且只在低频路径调用(如配置热重载、运维命令) -
Process(amount float64)用mu.RLock(),读多写少场景下比Mutex更高效 - HTTP handler 里禁止直接调用
SetStrategy()——一个请求改了,所有并发请求都受影响
简单场景直接用函数类型,别硬套接口
如果策略只是几行逻辑(比如字符串校验、排序比较),用 type ValidateFunc func(string) bool 更轻量。它天然支持闭包捕获依赖,测试时直接传个匿名函数就行。
- 优势:零接口开销、IDE 自动补全好、无需 mock
- 限制:没法附加字段或方法,不适合需要配置、重试、日志等复合行为的策略
- 混用可行:上层用函数类型,内部再组合策略接口,比如
func(input string) bool { return s.Validate(input) }
最难的从来不是怎么写接口,而是想清楚「谁创建策略」「谁决定换哪个」「老请求是否等新策略生效完再结束」——这些边界模糊时,SetStrategy() 调用一次,线上就可能出不可重现的竞态。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











