策略接口必须包含Init方法以支持配置解析与校验,注册需动态化并用工厂函数隔离状态,配置应分层解析且名称严格一致,启动时须校验注册完整性。

策略接口定义必须暴露配置解析能力
策略模式在 Go 中落地时,不能只关注行为抽象,还要让每个策略能自行加载和校验配置。否则配置系统会退化成硬编码分支——switch 一堆 string 类型名,既难扩展又无法验证。
推荐定义统一策略接口,包含 Init(config map[string]interface{}) error 方法:
type Strategy interface {
Name() string
Execute(data interface{}) error
Init(config map[string]interface{}) error // 关键:策略自己负责配置解析
}
- 避免由外部统一解析后传参,不同策略字段差异大,强耦合易出错
-
map[string]interface{}兼容 JSON/YAML 解析结果,也便于单元测试构造输入 - 在
Init中做字段存在性、类型校验(如要求config["timeout"]是float64),失败立即返回 error
策略注册需支持运行时动态加载
硬写 map[string]Strategy{...} 会阻碍热更新和插件化。实际项目中,策略可能来自独立模块或远程配置中心,必须支持延迟注册。
用 sync.Map + 函数注册表实现轻量注册中心:
立即学习“go语言免费学习笔记(深入)”;
var strategyRegistry = &sync.Map{}
func RegisterStrategy(name string, factory func() Strategy) {
strategyRegistry.Store(name, factory)
}
func GetStrategy(name string) (Strategy, error) {
if v, ok := strategyRegistry.Load(name); ok {
return v.(func() Strategy)(), nil
}
return nil, fmt.Errorf("unknown strategy: %s", name)
}
- 注册时机可放在
init()函数,也可在服务启动后从目录扫描.so插件(需plugin包) - 工厂函数
func() Strategy能隔离实例状态,避免并发误用单例 - 注意:Go 的
plugin不支持 Windows,生产环境优先走 HTTP 策略发现 + 本地注册
配置解析应与策略解耦但保留上下文传递
配置文件(如 YAML)里常含通用字段(enabled, retry, log_level)和策略特有字段(redis_addr, api_url)。直接把整个配置丢给策略的 Init 会导致职责混乱。
建议分层解析:
- 主配置器先提取公共字段,构建
Context结构体(含 logger、tracer、timeout 等) - 再将剩余字段(
strategy_config)作为子 map 传入策略的Init - 策略内部只处理自己关心的 key,不感知全局结构
示例片段:
type Config struct {
Enabled bool `yaml:"enabled"`
Retry int `yaml:"retry"`
LogLevel string `yaml:"log_level"`
Strategy map[string]interface{} `yaml:"strategy"`
}
func LoadAndInit(cfg Config, strategyName string) (Strategy, error) {
ctx := NewContext(cfg.Enabled, cfg.Retry, cfg.LogLevel)
if factory, err := GetStrategy(strategyName); err == nil {
s := factory()
// 只传 strategy 字段给策略自身解析
if err := s.Init(cfg.Strategy); err != nil {
return nil, err
}
return s, nil
}
return nil, fmt.Errorf("no strategy registered: %s", strategyName)
}
YAML 配置中的策略类型名必须与注册名严格一致
这是最常踩的坑:配置里写 type: redis_cache,但代码里注册的是 RegisterStrategy("RedisCache", ...),导致 GetStrategy 返回 nil。
- 注册名建议全小写 + 下划线(
"redis_cache"),和 YAML 键风格对齐 - 配置解析层应做名称标准化(trim 空格、转小写),但策略注册侧不要自动转换——显式比隐式更可靠
- 启动时遍历所有已注册策略名,对比配置中出现的
type值,缺失则 panic 或 fatal,别等到执行时才报错
配置校验逻辑可加在服务初始化末尾:
for _, typeName := range config.StrategyTypes {
if _, err := GetStrategy(typeName); err != nil {
log.Fatal("unregistered strategy in config: ", typeName)
}
}
策略配置系统真正的复杂点不在结构设计,而在于配置变更后的策略实例生命周期管理——比如旧策略正在处理请求,新配置已加载,如何安全切换?这需要结合 context 和 channel 控制,不是接口定义能解决的。










