StrategyChain需自定义,核心是统一Strategy接口(Apply方法)、StrategyChain结构体(持策略切片)及Run方法实现顺序执行与错误传播,支持ErrSkip跳过、context透传和类型安全契约。

StrategyChain 不是标准库类型,也不是 Go 内置概念——它得你自己定义。所谓“多阶段处理逻辑合并成一个可组合的计算函数”,本质是把多个策略按顺序串联执行,每个阶段接收前一阶段输出、产生下一阶段输入,并支持运行时增删或替换任意环节。这不是靠语法糖实现的,而是靠接口 + 组合 + 函数链式调用。
为什么不能直接用 func 链式调用?
单纯写 f1(f2(f3(data))) 看似链式,但问题明显:
- 无法动态插入/跳过某阶段(比如测试时绕过校验)
- 错误无法统一拦截或中断(
f2panic 了,f3根本不会执行,但上层难做兜底) - 每个函数签名必须严格匹配:输入/输出类型一致,否则编译失败
- 没有上下文透传能力(比如日志 traceID、超时控制),只能靠参数层层传,污染签名
StrategyChain 的核心结构怎么设计?
关键不是“链表”,而是统一的执行契约。推荐用以下最小接口:
type Strategy interface {
Apply(ctx context.Context, data interface{}) (interface{}, error)
}
所有阶段都实现这个接口,意味着:
- 输入统一为
context.Context + interface{}—— 兼容任意数据类型,也支持取消和超时 - 输出统一为
(interface{}, error)—— 错误可被链上任一环节检查并决定是否继续 - 每个策略自己决定是否修改
data,或返回新实例(推荐不可变)
链本身就是一个结构体,持有一个 []Strategy 切片:
立即学习“go语言免费学习笔记(深入)”;
type StrategyChain struct {
strategies []Strategy
}
func (c *StrategyChain) Use(s Strategy) *StrategyChain {
c.strategies = append(c.strategies, s)
return c
}
func (c *StrategyChain) Run(ctx context.Context, data interface{}) (interface{}, error) {
for _, s := range c.strategies {
var err error
data, err = s.Apply(ctx, data)
if err != nil {
return nil, err
}
}
return data, nil
}
如何让策略支持条件跳过或短路?
纯接口无法表达“跳过”语义,所以需要约定一种错误类型作为信号。常见做法:
- 定义一个哨兵错误:
var ErrSkip = errors.New("skip this strategy") - 某个策略在满足条件时返回
nil, ErrSkip,链中捕获后忽略该错误、继续下一轮 - 若需中断整个链(如鉴权失败),就返回普通 error,
Run立即退出
注意:不要用 panic 做流程控制 —— 它无法被链统一 recover,且违背 Go 的错误处理惯例。
实际组装时容易漏掉的关键点
真实业务里最常出问题的不是链本身,而是策略之间的数据契约隐含依赖:
- 前一策略返回
map[string]interface{},后一策略却硬转成*User—— 类型断言失败 panic - 某个策略修改了传入的 map,后续策略读到脏数据(Go 中 map 是引用类型)
- 链初始化后未做防御性拷贝,外部修改了原始
strategies切片,导致并发调用行为不一致
解决方式很朴素:在 Run 开始前 shallow copy 切片;所有策略文档注明输入/输出类型;必要时加 runtime type check(比如 _, ok := data.(MyInput))。


















