go策略模式通过接口契约+构造函数注入实现,paymentstrategy接口仅定义pay(amount float64) error方法,避免init/close等冗余方法,不使用interface{}存储,newalipay返回接口类型并校验依赖非空,运行时切换需rwmutex保护。

Go 语言里策略模式不是靠继承或抽象类,而是靠接口契约 + 构造函数注入 + 字段赋值来实现的;错一处就容易 panic 或并发不安全。
怎么定义 PaymentStrategy 接口才不会 runtime panic
接口只保留真正会变的行为,比如 Pay(amount float64) error ——加 Init()、Close()、SetLogger() 都是典型错误。这些方法会让所有实现被迫写空逻辑,还破坏“小接口”原则。
- 接口名用名词,如
PaymentStrategy,别写IPaymentStrategy(Go 社区不用I前缀) - 方法必须导出(首字母大写),否则其他包无法实现
- 别提前塞
ctx context.Context参数——没上下文需求时硬加,等于给所有实现埋修改成本 - 绝对不用
interface{}或map[string]interface{}存策略,类型断言失败是panic高发区
为什么 NewAlipay() 必须返回接口类型而不是结构体指针
返回 *Alipay 会让调用方感知具体类型,一旦要替换成 UnionPay,就得改所有 new 和赋值语句;而返回 PaymentStrategy,上层只认接口,切换只需改初始化那一行。
- 构造函数统一用
NewXXX命名,缩写全大写(如NewAlipay,不是NewAliPay) - 依赖必须传入并校验非空:比如
client *http.Client为 nil 时直接返回 error,别等到Pay()里才 panic - 初始化失败要返回
(PaymentStrategy, error),不是panic——上游需要知道“策略不可用”,而不是服务崩掉 - 别在构造函数里做耗时操作(如建 HTTP 连接、读配置文件),策略应轻量,状态可控
运行时切换策略为什么不能用全局变量 currentStrategy
HTTP handler 里直接改 var currentStrategy PaymentStrategy,等于给服务埋雷:多个 goroutine 同时写会触发 data race,Go runtime 明确禁止。
- 正确做法是把策略作为结构体字段封装,用
sync.RWMutex保护读写 - 读多写少场景下,
RWMutex比Mutex性能更好 - 如果策略启动后就固定(比如从配置加载一次),用
sync.Once初始化即可,无需锁 - 绝对不要在 handler 里调用
SetStrategy()——一个请求改了,所有后续请求都跟着变
怎么避免策略实例被多个请求共享导致状态污染
策略对象如果带状态(比如 retryCount int 字段),多个请求共用同一个实例,就会相互干扰:A 用户重试了 2 次,B 用户一上来就带着 retryCount=2 开始,直接跳过重试逻辑。
- 策略应尽量无状态;如有必要保存上下文,状态必须隔离到每次调用中(比如通过参数传入
ctx或reqID) - 别把策略实例存在全局 map 或单例里长期复用,尤其在 HTTP 服务中
- 测试时用手工 mock 实现(如
type MockPay struct{ Called bool }),聚焦行为选择,不依赖网络或真实 client - 注册表优于 if-else 路由:用
map[string]PaymentStrategy配合工厂函数,新增策略只需注册,不碰业务分支逻辑
最常被忽略的一点:策略不是“算法容器”,而是有明确生命周期边界的协作单元。它不负责初始化外部依赖,也不该承担日志、监控、重试等横切关注点——这些应该由调用方或中间件统一处理。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











