Go语言泛型使用场景
涛叔我已经写过多篇介绍泛型的文章。但跟所有技术一样,泛型也有其适用的范围。今天就讨论一下什么时候应该使用泛型,什么时候又不该使用它。
先说应该使用泛型的场景。
合适的场景
函数式编程
第一个场景就是函数式编程。如果你的函数是操作 slice/map/chan 等对象,但不关心每个元素的类型,那就很适合使用泛型。最典型的就是过滤 slice 中的元素,提取 map 的 key 或者 value,合并不同的 channel 等等。这在我的泛型示例一文中都有具体的示例代码。
通用数据结构
第二个场景就是通用数据结构。比如链表、堆、树、图等结构。如果没有泛型,就只能将参数的类型定为interface{}
,这样就无法利用编译器来检查类型错误。
比如现在Go语言的container/list
包,其Element
定义如下:
type Element struct {
interface{}
Value }
如果有了泛型,那就可以改为:
type Element[T any] struct {
Value T}
从而支持各种类型的数据结构。
在使用泛型的时候,优先使用泛型函数而非泛型成员方法。比如下面的二叉树定义:
type Tree[T any] struct {
func(T, T) int
cmp *leaf[T]
root }
type leaf[T any] struct {
,
val T, right *leaf[T]
left}
func NewTree[T any](vars []T, cmp func(T, T) bool) *Tree[T] {
// ...
}
Tree
中的类型参数T
使用any
约束,比较方法cmp
需要单独指定。这样使用起来会更加方便,比如针对int
类型,我们可以:
:= NewTree([]int{1,2,3,4}, func(a, b int) { return a < b }) t
但如果我们将T
限定为interface{cmp(T, T) bool}
,那么,我们就不能简单地初始化一个int
类型的二叉树,因为int
类型没有实现cmp
方法。为此,我们只能自定义一个特殊的int
类型,然后给它加cmp
成品方法才能再构建二叉树。
大量相似逻辑
第三个场景就是所有成员函数的逻辑非常相似。
以排序为例,sort
包要求被排序的列表实现sort.Interface
接口。通常,列表都使用切片表示,而对应的sort.Interface
实现也都非常相似:
type ints []int
type (is ints) Len() int { return len(is) }
type (is ints) Swap(i, j int) { is[i],is[j] = is[j],is[i] }
type (is ints) Less(i, j int) bool { ... }
对于任意类型的切片,Len和Swap方法完全一样,而 Less 方法也只需提供不同的比较逻辑即可。这种场景就特别适合使用泛型来简化代码:
type sliceFn[T any] struct {
[]T
s func(T, T) bool
cmp }
func (s sliceFn[T]) Len() int { return len(s.s) }
func (s sliceFn[T]) Less(i, j int) bool { return s.cmp(s.s[i], s.s[j]) }
func (s sliceFn[T]) Swap(i, j int) { s.s[i], s.s[j] = s.s[j], s.s[i] }
func SliceFn[T any](s []T, cmp func(T, T) bool) {
.Sort(sliceFn[T]{s, cmp})
sort}
使用方法如下:
.SliceFn([]int{1,2,3}, func(a, b int) bool { return a < b})
sort.SliceFn([]string{"a","b","c"}, func(a, b string) bool { return a < b}) sort
以上是适合使用泛型的场景。下面讲一下不适合的场景。
不合适的场景
只调函数不返回类型
目前只有一个场景,就是只调参数的一个方法,比如:
func ReadFour[T io.Reader](r T) (buf []byte, err error) {
= make([]byte, 4)
buf , err = r.Read(buf)
n// ...
}
这一类情况完全不需要使用泛型,应该使用接口:
func ReadFour(r io.Reader) (buf []byte, err error) {
// ...
}
总结
如果使用泛型会让代码变得更复杂,那也应该避免。大家可以尝试我在前传一文中提到的替代方法。总之,大家在使用的时候要充分要考虑泛型的使用场景,不要一把梭。
参考链接: