本文小编为大家详细介绍“怎么利用Golang泛型提高编码效率”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么利用Golang泛型提高编码效率”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
工具函数
虽然标准库里面已经提供了大量的工具函数,但是这些工具函数都没有使用泛型实现,为了提高使用体验,我们可以使用泛型进行实现。
比如数值算法里很经典的
math.Max()、
math.Min()都是
float64类型的,但是很多时候我们使用的是
int、
int64这些类型,在Golang引入泛型之前,我们经常像下面这样根据类型实现,产生大量模板代码:
func MaxInt(a, b int) int {
    if a > b {
        return a
    }
    return b
}
func MaxInt64(a, b int64) int64 {
    if a > b {
        return a
    }
    return b
}
// ...其他类型而使用泛型则我们只需要一个实现:
func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}其中
constraints.Ordered表示可排序类型,也就是可以使用三路运算符的类型
[>, =, <],包含了所有数值类型和
string。可以通过
go get golang.org/x/exp引入。
代码地址
其他的像json解析、参数校验、slices等也可以通过泛型进行实现。
数据结构
Golang自带的泛型容器有slices和map,这两个数据结构其实可以完成大部分工作了,但是有时候我们可能还需要其他的数据结构,比如说优先级队列、链表等。
虽然Golang在
container包下有
heap、
list和
ring三个数据结构,但说实话使用起来不是很方便,特别是元素类型全是
interface{},使用这些结构就需要各种类型转换。因此我们可以简单的拷贝这些代码,然后使用泛型进行改造,比如heap:我们不但使用泛型进行实现,还把heap默认改为使用slice是实现,这样只需要实现一个LessFunc,而不是5个。
package heap
type LessFunc[T any] func(e1 T, e2 T) bool
type Heap[T any] struct {
    h        []T
    lessFunc LessFunc[T]
}
func New[T any](h []T, lessFunc LessFunc[T]) *Heap[T] {
    heap := &Heap[T]{
        h:        h,
        lessFunc: lessFunc,
    }
    heap.init()
    return heap
}
// 移除堆顶元素
func (h *Heap[T]) Pop() T {
    n := h.Len() - 1
    h.swap(0, n)
    h.down(0, n)
    return h.pop()
}
// 获取堆顶元素
func (h *Heap[T]) Peek() T {
    return h.h[0]
}
// 添加元素到堆
func (h *Heap[T]) Push(x T) {
    h.push(x)
    h.up(h.Len() - 1)
}代码地址
其他的数据结构还包括list、set、pqueue等。
模板代码
在后台业务代码里面,我们经常会有很多个业务处理函数,每个业务处理函数我们基本都会通过一些代码封装成一个HTTP接口,这里其实基本上都是模板代码,比如说对于一个使用gin实现的
HTTP服务,每个接口我们都需要进行以下处理:
- 指定HTTP方法、URL 
- 鉴权 
- 参数绑定 
- 处理请求 
- 处理响应 
可以发现,参数绑定、处理响应几乎都是一样模板代码,鉴权也基本上是模板代码(当然有些鉴权可能比较复杂)。
因此我们可以编写一个泛型模板,把相同的部分抽取出来,用户只需要实现不同接口有差异的指定HTTP方法、URL和处理请求逻辑即可:
// 处理请求
func do[Req any, Rsp any, Opt any](reqFunc ReqFunc[Req],
    serviceFunc ServiceFunc[Req, Rsp], serviceOptFunc ServiceOptFunc[Req, Rsp, Opt], opts ...Opt) gin.HandlerFunc {
    return func(c *gin.Context) {
        // 参数绑定
        req, err := BindJSON[Req](c)
        if err != nil {
            return
        }
        // 进一步处理请求结构体
        if reqFunc != nil {
            reqFunc(c, req)
        }
        var rsp *Rsp
        // 业务逻辑函数调用
        if serviceFunc != nil {
            rsp, err = serviceFunc(c, req)
        } else if serviceOptFunc != nil {
            rsp, err = serviceOptFunc(c, req, opts...)
        } else {
            panic("must set ServiceFunc or ServiceFuncOpt")
        }
        // 处理响应
        ProcessRsp(c, rsp, err)
    }
}这样,现在一个接口基本上只需要一行代码即可实现(不包括具体业务逻辑函数):
   // 简单请求,不需要认证
    e.GET("/user/info/get", ginrest.Do(nil, GetUserInfo))
    // 认证,绑定UID,处理
        reqFunc := func(c *gin.Context, req *UpdateUserInfoReq) {
        req.UID = GetUID(c)
    } // 这里拆多一步是为了显示第一个参数是ReqFunc
    e.POST("/user/info/update", Verify, ginrest.Do(reqFunc, UpdateUserInfo))代码地址,实现了一个基于gin的RESTful风格模板。
对象池/缓存
Golang标准库自带了一个线程安全、高性能、还能够根据对象热度自动进行释放的对象池
sync.Pool,然而作为对象池,我们一般只会往里面放一种类型的对象,但
sync.Pool里面的元素还是
interface{}类型,因此我们可以简单的封装sync.Pool,让它里面的元素有具体类型:
这里其实就是简单的对象
sync.Pool进行包装,然后添加了一个
ClearFunc()在回收对象的时候进行一些清理操作,比如说
byte切片我们需要让它的已用长度归零(容量还是不变)。
// 创建新对象
type NewFunc[T any] func() T
// 清理对象
type ClearFunc[T any] func(T) T
type Pool[T any] struct {
    p         sync.Pool
    clearFunc ClearFunc[T]
}
func New[T any](newFunc NewFunc[T], clearFunc ClearFunc[T]) *Pool[T] {
    if newFunc == nil {
        panic("must be provide NewFunc")
    }
    p := &Pool[T]{
        clearFunc: clearFunc,
    }
    p.p.New = func() any {
        return newFunc()
    }
    return p
}
// 获取对象
func (p *Pool[T]) Get() T {
    return p.p.Get().(T)
}
// 归还对象
func (p *Pool[T]) Put(t T) {
    if p.clearFunc != nil {
        t = p.clearFunc(t)
    }
    p.p.Put(t)
}作为字节数组对象池使用:
    newFunc := func() []byte {
        return make([]byte, size, cap)
    }
    clearFunc := func(b []byte) []byte {
        return b[:0]
    }
    p := New(newFunc, clearFunc)
    bytes := p.Get() // 这里bytes类型是[]byte
    p.Put(bytes)代码地址
对于缓存也是同理,目前大部分缓存库的实现都是基于
interface{}或者是byte[],但是我们还是更加喜欢直接操作具体类型,因此我们可以自己使用泛型实现(或改造)一个缓存库。我自己也实现了一个泛型缓存策略库,里面包含LRU、LFU、ARC、NearlyLRU、TinyLFU等缓存策略。