PaymentStrategy接口必须窄(仅Pay(amount float64) error),否则切换渠道需连锁修改所有实现;策略实例须无共享状态,避免并发污染;注册表需并发保护,禁用map[string]interface{}查表。

PaymentStrategy 接口必须定义清晰、窄契约,否则切换支付渠道时会连锁修改所有实现;策略实例不能复用,否则并发请求间状态互相污染;注册表若没做并发保护,查策略时可能 panic。
接口定义要窄,别往 Pay 方法里塞 context 或 orderID
很多初学者一上来就在接口里加一堆参数:Pay(ctx context.Context, orderID string, amount float64, currency string)。这导致每新增一个支付渠道,就得同步改所有已实现的 Pay 方法——根本不是“可插拔”,是“牵一发而动全身”。
- 正确做法:只保留核心输入,比如
Pay(amount float64) error,其他字段(如orderID)由策略 struct 自己持有或通过构造函数注入 - 如果真需要
context,统一加在接口方法签名最前面,且所有实现必须适配——但建议优先用中间件包装,而不是污染策略契约 - 避免在接口里暴露
Refund、Query等非核心行为;不同支付渠道这些能力差异大,不该强求一致
策略 struct 必须无共享状态,别用单例全局变量
常见错误是把 *http.Client 或重试计数器写成全局变量,或者让多个请求共用同一个 Alipay 实例。结果 A 用户支付失败触发了重试逻辑,B 用户紧接着调用就直接用了残留的 retryCount=2。
- 每个请求应获得独立策略实例:用
NewAlipay(apiKey, secret)构造,而不是var alipay = &Alipay{...}全局声明 - 若需复用底层资源(如 HTTP client),把它作为依赖注入进 struct 字段,而不是靠全局单例
- 测试时能一眼看出状态是否隔离:跑并行测试
go test -race不报 data race,才算过关
多支付渠道要用注册表,别写 switch-case 硬编码
当支持支付宝、微信、PayPal、Stripe、Crypto 五种渠道后,if name == "alipay" { ... } else if name == "wechat" { ... } 就成了维护噩梦,每次新增都要改判断逻辑。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 注册表用
map[string]func() PaymentStrategy,初始化时调用Register("alipay", NewAlipay) - 查表逻辑封装成
GetStrategy(name string) (PaymentStrategy, bool),返回nil, false而不是 panic - 注册发生在
init()或main()开头,查表用sync.RWMutex保护,或直接用sync.Once初始化只读 map - 别用
map[string]interface{}+ 类型断言——IDE 不提示、运行时报 panic、重构时完全不可知
轻量策略直接用函数类型,别为一行逻辑硬造 struct
比如测试用的 mock 支付:func(amount float64) error { log.Println("mock pay:", amount); return nil }。非要套一层 type MockPay struct{} 再实现接口,纯属增加认知负担。
立即学习“go语言免费学习笔记(深入)”;
- 定义
type PayFunc func(amount float64) error,它本身就能当策略用 - 闭包可预置配置:
func(apiKey string) PayFunc { return func(a float64) error { ... } } - 缺点也很明确:没法附带元数据(如
strategy.Name())、无法嵌入其他接口、不支持组合——这时候就该切回 struct + 接口方案
userID,支付主策略需要 orderID,两者都依赖 context.Context,但又不能让 Pay() 方法签名膨胀。解决方案不是加参数,而是让上层组合时统一注入——这已经超出策略模式本身,进入策略链(Chain of Responsibility)的范畴了。

















