链式过滤是通过自定义pipeline类型实现的可累积过滤器结构,where方法返回*pipeline[t]支持连续调用,apply方法延迟执行并仅遍历一次原始数据,避免中间切片分配。

什么是链式过滤,Go 里没有原生支持
Go 语言本身不提供类似 JavaScript 的 .filter().map().reduce() 链式调用语法,因为方法链依赖返回接收者自身(如 *Builder),而切片是值类型,直接返回新切片无法延续链式调用。所以“链式过滤”必须手动构造可组合的函数结构,核心是让每个过滤器返回一个能继续调用下一个过滤器的中间对象。
用函数类型封装过滤逻辑,避免切片拷贝失控
常见错误是每层过滤都生成新切片并返回——看似链式,实则性能差、内存浪费大,且无法真正“延迟执行”。正确做法是定义一个可累积的过滤器类型:
type FilterFunc[T any] func(T) bool
type Pipeline[T any] struct {
filters []FilterFunc[T]
}
func (p *Pipeline[T]) Where(f FilterFunc[T]) *Pipeline[T] {
p.filters = append(p.filters, f)
return p
}
func (p *Pipeline[T]) Apply(data []T) []T {
result := make([]T, 0, len(data))
for _, item := range data {
pass := true
for _, f := range p.filters {
if !f(item) {
pass = false
break
}
}
if pass {
result = append(result, item)
}
}
return result
}
关键点:
-
Where返回*Pipeline[T],支持连续调用 - 所有过滤条件在
Apply时才真正遍历一次原始数据,避免中间切片分配 - 注意:
Apply是终态方法,不返回*Pipeline,链在此结束 - 如果数据量极大且过滤条件复杂,可考虑加
context.Context支持中断
如何与标准库 slice 包协同使用
Go 1.21+ 引入了 golang.org/x/exp/slices(后移入 std/slices),但它只提供单次操作函数(如 slices.Filter),不支持链式。若你已习惯用 slices.Filter,别强行套链式——它更轻量、更易测试、更符合 Go 的显式风格。只有当业务中存在固定多步过滤逻辑(如 “用户列表 → 按状态过滤 → 按部门过滤 → 按权限过滤”)且复用频繁时,才值得抽象为 Pipeline。
混用示例(不推荐但可行):
users := []User{...}
p := NewPipeline[User]()
p.Where(func(u User) bool { return u.Status == "active" })
p.Where(func(u User) bool { return u.Dept == "eng" })
// 最后用 slices.Filter 做最终筛选(破坏链式,但利用标准库优化)
filtered := slices.Filter(p.Apply(users), func(u User) bool { return u.HasAdminRole })
这种混用容易让人困惑:到底谁负责执行?建议统一风格,要么全用自定义 Pipeline,要么就用 slices.Filter + 函数组合(andThen 风格)。
容易被忽略的泛型约束和 nil 安全问题
泛型 [T any] 足够宽泛,但实际中常需限制为可比较或非接口类型(比如你要在过滤器里做 item != nil 判断)。这时得加约束:
type Pipeline[T comparable] struct { ... }
更隐蔽的问题是:如果传入的 data 为 nil,range 不会 panic,但 len(data) 为 0,make(..., 0, len(data)) 会得到 nil 切片——这通常没问题;但如果下游代码用 cap(result) > 0 做判断,就会出错。稳妥写法是:
result := make([]T, 0)
if data != nil {
result = make([]T, 0, len(data))
}
另外,FilterFunc[T] 本身不检查参数有效性,若 T 是指针类型且含 nil 值,过滤器内部必须自行判空,否则可能 panic。
链式本身不难写,难的是想清楚:什么时候该链,什么时候该拆成独立函数;以及是否真需要把过滤逻辑从数据处理流程中抽离出来——多数时候,一个清晰的 for 循环比花哨的链更可靠。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











