Go 中 Strategy 模式应以函数值替代接口,用 map 或 switch 分发,策略只负责纯业务逻辑,决策与执行解耦,日志重试等横切关注点由外层统一处理。

Strategy 模式在 Go 里不是用来套接口+工厂+注册表的,而是帮你把一堆 if-else 或 switch 拆干净、跑得稳、加得快——前提是别把它当 Java 用。
用函数值代替接口,别先定义 Strategy 接口
Go 没有虚函数调度开销,也不靠运行时反射找实现。硬写一个 type PaymentStrategy interface { Pay(amount float64) error },再塞七八个 struct{} 去实现它,纯属增加编译单元和 nil panic 风险。
直接用函数类型更轻、更安全:
type PayHandler func(ctx context.Context, amount float64) error
<p>var handlers = map[string]PayHandler{
"alipay": func(ctx context.Context, a float64) error { /<em> ... </em>/ },
"wechat": func(ctx context.Context, a float64) error { /<em> ... </em>/ },
"paypal": func(ctx context.Context, a float64) error { /<em> ... </em>/ },
}
- 函数签名统一,IDE 可跳转、可内联、可测试
- 无状态,天然并发安全(只要不闭包捕获可变变量)
- 新增策略只需往 map 字面量里加一行,不用改接口、不用补实现、不用注册逻辑
- 查不到 key 时会得到
nil,必须显式判断ok,否则调用 panic —— 这反而是好事,暴露缺失分支
判断逻辑必须抽离,和策略实现彻底解耦
你真正该重构的,从来不是“怎么执行”,而是“怎么决定执行哪个”。把 req.PaymentMethod == "alipay" 这类判断硬塞进 handler 里,或者和策略注册混在一起,等于又造了个隐形 if-else 链。
立即学习“go语言免费学习笔记(深入)”;
单独抽一个分发函数:
func selectHandler(req *PaymentRequest) string {
switch {
case req.UserTier == "vip" && req.Amount > 1000:
return "paypal_vip"
case req.Country == "CN":
return req.PaymentMethod
default:
return "alipay"
}
}
- 返回字符串 key,只负责路由,不碰业务逻辑
- 单元测试可覆盖所有分支组合,不依赖具体 handler 实现
- 后续加灰度、AB 测试、降级开关,全在这一个函数里改,不影响策略本体
少于 5 个策略?直接用 switch,别 map 查找
map 查找有哈希计算、指针间接寻址;而 switch 在 Go 里对小整数或短字符串是跳转表或二分查找,更快、更省内存、编译期可验证全覆盖。
比如支付方式就三个:
func handlePayment(ctx context.Context, req *PaymentRequest) error {
switch req.PaymentMethod {
case "alipay":
return alipayHandler(ctx, req.Amount)
case "wechat":
return wechatHandler(ctx, req.Amount)
case "paypal":
return paypalHandler(ctx, req.Amount)
default:
return fmt.Errorf("unsupported method: %s", req.PaymentMethod)
}
}
- 没有 map 初始化、没有 key 存在性检查、没有类型断言开销
- IDE 能高亮未处理的
case,go vet也能报出遗漏分支(配合 -shadow) - 函数内联友好,编译器更容易做优化
策略里别塞日志、metric、重试、panic
这是最容易让策略失控的地方。一旦每个 handler 都自己打日志、自己 recover、自己重试三次,那你就没策略了,只有八九个长得像的业务模块。
策略只干一件事:给输入,还输出,出错就返回 error。
- 日志、metric、trace、重试、超时,全由外层统一包装(比如 middleware 或 decorator)
- 错误类型保持纯净:
errors.Is(err, ErrInsufficientBalance)才能被上层识别并走特定降级路径 - 如果策略真需要依赖(如 HTTP client、DB conn),通过闭包注入,而不是塞进 struct 字段里搞状态共享
最常被忽略的点:策略不是“算法容器”,而是“契约边界”。边界划歪了,再多的模式也救不回可维护性——比如把「要不要记录失败次数」这种决策放进策略里,本质上就把限流逻辑和支付逻辑耦死了。


















