必须用 interface;因函数类型无法保存状态(如缓存、配置),而 struct 实现 interface 可携带字段,支持日志、重试、依赖注入等复杂场景。

Strategy 接口定义必须用函数类型还是 interface?
Go 没有传统 OOP 的抽象类或虚函数,策略模式靠组合而非继承。核心是定义一个统一的 Execute 行为契约,但实现方式有两种常见误用:一种是用函数类型(如 type Strategy func(int) int),另一种是用 interface(如 type Strategy interface { Execute(int) int })。前者简洁,后者更灵活——尤其当你需要在策略里携带状态(比如缓存、配置、连接池)时,函数类型无法保存字段,而 struct 实现 interface 就可以。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 如果策略纯无状态、逻辑极简(如简单数学运算),用
func(int) int类型最轻量 - 只要涉及日志、重试、超时、依赖注入(如传入
*http.Client),必须用interface{ Execute(...) ... }+ 具体 struct 实现 - 别把 interface 定义得过大;只包含当前上下文真正需要的方法,比如不需要
Init()或Close()就别加
运行时替换策略时如何避免竞态和 panic?
直接用全局变量或包级变量存当前策略,然后在运行时赋新值,是最容易想到的做法,但也是最容易出问题的路径。常见错误现象:panic: runtime error: invalid memory address or nil pointer dereference(旧策略被置 nil 后还没来得及更新,另一 goroutine 就调了 Execute);或执行中策略突变导致中间状态不一致。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
sync.RWMutex保护策略变量读写:读多写少场景下,RLock()/RLock()开销小,比全用Mutex更合适 - 替换策略时,确保新策略非 nil:在
SetStrategy方法里加if s == nil { panic("strategy cannot be nil") } - 不要在策略方法内部再修改自身字段(比如
self.cache = nil),除非你明确控制了并发访问——否则应把可变状态封装进独立的、带锁的结构体
如何让策略支持参数化配置而不破坏接口一致性?
硬编码策略行为(比如固定超时 5s)会导致每次改配置就得重新编译;但若把配置项塞进 Execute 参数,又会让所有策略实现都得处理无关字段,违背策略隔离原则。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 把配置作为策略构造函数的输入,而不是
Execute的参数。例如:NewRetryStrategy(WithMaxRetries(3), WithBackoff(100*time.Millisecond)) - 用函数式选项(Functional Options)模式封装配置,避免构造函数参数爆炸;每个 option 是个
func(*retryStrategy) - 策略 struct 内部字段应设为 unexported(小写),防止外部直接修改;配置只在初始化时设定一次,运行时只读
- 如果真需要运行时动态调参(如限流阈值),单独暴露一个线程安全的 setter,比如
strategy.SetRateLimit(newQPS),内部用atomic.StoreInt64或sync.Once控制
为什么不用 map[string]Strategy 做策略注册表反而更稳妥?
看到“运行时替换”,很多人第一反应是建个 map[string]Strategy,用名字查策略,再用字符串切换。这看似灵活,实则埋坑:拼错 key 导致 fallback 到默认策略却无提示;类型擦除后无法静态检查策略是否实现了全部方法;热加载时 key 冲突或未注册引发 panic。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 优先用显式变量或字段持有当前策略,比如
processor.strategy Strategy,靠代码逻辑控制生命周期 - 如果确实需要多策略共存+按需选,用 const 枚举代替字符串:
const StrategyA StrategyType = "a",配合 switch 判断,编译期可检错 - map 只用于策略工厂缓存(如
map[StrategyType]func() Strategy),且只在初始化阶段填充,不用于运行时高频查找 - 任何从外部(如 config file、HTTP 请求)读取的策略标识,必须经过白名单校验,否则拒绝启动
策略不是越动态越好。真正的复杂点在于:何时该换、谁负责通知、换完要不要等正在执行的任务结束。这些没法靠接口定义解决,得结合业务生命周期设计退出协调机制。










