示例

首先模拟一个业务场景,有订单、产品、自定义订单三个结构体,订单中包含多个产品:

 1type Order struct {
 2	Id       string
 3	Products []Product
 4}
 5
 6type Product struct {
 7	Id    string
 8	Price int
 9}
10
11type CustomOrder struct {
12	Id string
13}

初始化模拟数据:

 1var orders = []Order{
 2	{
 3		Id: "o1",
 4		Products: []Product{
 5			{
 6				Id:    "p1",
 7				Price: 1,
 8			},
 9			{
10				Id:    "p2",
11				Price: 2,
12			},
13		},
14	},
15	{
16		Id: "o2",
17		Products: []Product{
18			{
19				Id:    "p3",
20				Price: 3,
21			},
22			{
23				Id:    "p4",
24				Price: 4,
25			},
26		},
27	},
28}

接下来对订单列表做各种操作:

 1// 过滤Id为o2的订单
 2func TestFilter(t *testing.T) {
 3	res := Lists[Order](orders).Filter(func(o any) bool {
 4		return o.(Order).Id == "o2"
 5	}).Collect()
 6	t.Log(res) // [{o2 [{p3 3} {p4 4}]}]
 7}
 8
 9// 将订单列表映射为自定义订单列表
10func TestMap(t *testing.T) {
11	res := Lists[CustomOrder](orders).Map(func(o any) any {
12		return CustomOrder{
13			Id: "custom-" + o.(Order).Id,
14		}
15	}).Collect()
16	t.Log(res) // [{custom-o1} {custom-o2}]
17}
18
19// 将每个订单里的产品展开,并映射为自定义订单
20func TestFlatAndMap(t *testing.T) {
21	res := Lists[CustomOrder](orders).
22		Flat(func(o any) []any {
23			return Lists[any](o.(Order).Products).ToList()
24		}).
25		Map(func(p any) any {
26			return CustomOrder{
27				Id: "ProductId-" + p.(Product).Id,
28			}
29		}).Collect()
30	t.Log(res) // [{ProductId-p1} {ProductId-p2} {ProductId-p3} {ProductId-p4}]
31}
32
33// 找到所有订单产品中价格最贵的那个产品
34func TestMax(t *testing.T) {
35	res, found := Lists[Product](orders).
36		Flat(func(o any) []any {
37			return Lists[any](o.(Order).Products).ToList()
38		}).
39		Max(func(i, j any) bool {
40			return i.(Product).Price > j.(Product).Price
41		})
42	t.Log(found, res) // true {p4 4}
43}

原理

1type List[T any] struct {
2	list []any
3}

将 go 中的原生切片包装成 List[T] 结构体,特别说明其中的泛型 T 是最终结果的元素类型,并不是原始传入切片的类型。

这样设计是因为 go 只能在构造结构体时指定泛型,因此将 List[T] 的泛型指定为最终结果的元素类型,就可以在操作完成后调用 Collect() 方法,得到最终的 T 类型切片,方便后面的业务逻辑使用。

因为 go 不支持在接受者函数中定义泛型,因此所有操作函数的参数和返回值类型只能定义为any,然后在函数体内转换为业务结构体使用,例如上面的 i.(Product).Price

此后将每一种操作,例如Filter、Map、Flat等,都返回List[T] 结构体,就可以实现链式操作。

实现

 1type List[T any] struct {
 2	list []any
 3}
 4
 5func Lists[T any](items any) *List[T] {
 6	rv := reflect.ValueOf(items)
 7	if rv.Kind() != reflect.Slice {
 8		panic(fmt.Sprintf("not supported type: %v, please use slice instead", rv.Kind()))
 9	}
10	l := rv.Len()
11	s := make([]any, 0, l)
12	for i := 0; i < l; i++ {
13		s = append(s, rv.Index(i).Interface())
14	}
15	return &List[T]{
16		list: s,
17	}
18}
19
20func (s *List[T]) Filter(fn func(any) bool) *List[T] {
21	l := make([]any, 0)
22	for _, e := range s.list {
23		if fn(e) {
24			l = append(l, e)
25		}
26	}
27	s.list = l
28	return s
29}
30
31func (s *List[T]) Map(fn func(any) any) *List[T] {
32	l := make([]any, 0)
33	for _, element := range s.list {
34		l = append(l, fn(element))
35	}
36	return &List[T]{
37		list: l,
38	}
39}
40
41func (s *List[T]) Flat(fn func(any) []any) *List[T] {
42	l := make([]any, 0)
43	for _, element := range s.list {
44		l = append(l, fn(element)...)
45	}
46	return &List[T]{
47		list: l,
48	}
49}
50
51func (s *List[T]) Sort(fn func(i, j any) bool) *List[T] {
52	if len(s.list) <= 0 {
53		return s
54	}
55	sort.SliceStable(s.list, func(i, j int) bool {
56		return fn(s.list[i], s.list[j])
57	})
58	return s
59}
60
61func (s *List[T]) Max(fn func(i, j any) bool) (T, bool) {
62	return s.Sort(fn).FindFirst()
63}
64
65func (s *List[T]) FindFirst() (T, bool) {
66	if len(s.list) <= 0 {
67		var nonsense T
68		return nonsense, false
69	}
70	return s.list[0].(T), true
71}
72
73func (s *List[T]) ToList() []any {
74	return s.list
75}
76
77func (s *List[T]) Collect() []T {
78	t := make([]T, 0)
79	for _, a := range s.list {
80		t = append(t, a.(T))
81	}
82	return t
83}