文章

Golang泛型的应用:约束传入对象拥有的方法

问题

在开发后台应用时,有比较多重复的查询数据库的请求,我数据库查询是基于gorm gen实现的,所有的查询请求中都包含page、limit、order、desc,也就是分页查询的参数,对于每一个请求都根据查询表的不同来构造查询语句实在是太麻烦,太冗长了,所有想到了实现一个通用的根据分页参数构造查询的函数,但是遇到了问题,gen的查询语句构造是链式调用,函数返回的对象都是不一样的interface,导致没有办法直接使用。

type QueryBaseReq struct {  
    Page  int    `json:"page" validate:"required"`  
    Limit int    `json:"limit" validate:"required"`  
    Order string `json:"order"`  
    Desc  bool   `json:"desc"`  
}

解决方案

使用golang的泛型,约束传入的对象拥有哪些方法,并且这些方法返回的值就是这个类型本身,在我们当前的这个需求当中,我们需要约束传入的查询对象拥有Limit、Order、Offset方法,因此我们创建了一个interface

type IDo[T any] interface {  
    Limit(_ int) T  
    Order(_ ...field.Expr) T  
    Offset(_ int) T  
}  

interface拥有所需的函数且使用泛型T,对T没有任何要求。

创建查询函数DoQuery

// DoQuery 根据查询条件构造查询  
func DoQuery[T IDo[T]](query QueryBaseReq, do T, model interface {GetFieldByName(fieldName string) (field.OrderExpr, bool)}) (T, error) {  
    order, ok := model.GetFieldByName(query.Order)  
    if ok {  
       if query.Desc {  
          do = do.Order(order.Desc())  
       } else {  
          do = do.Order(order.Asc())  
       }  
    } else {  
       return do, errors.New("排序字段不存在")  
    }  
    do = do.Limit(query.Limit).Offset(query.Page * query.Limit)  
    return do, nil  
}

该查询函数使用泛型T,对T的要求是满足interface IDo约束实现的三个方法,而interface IDo需要传入泛型约束,也还是T,以实现链式调用。

第一个参数query是查询参数,第二个参数do是传入的查询语句构造对象,类型就是泛型T,第三个参数model,类型约束为拥有{GetFieldByName(fieldName string) (field.OrderExpr, bool)}方法以获取排序Filed

这篇文章主要是记录如何在多个不同interface但是拥有部分类似方法实现的情况下,仅约束函数参数实现这部分类似方法,实现通用方法。

type QueryBaseReq struct {  
    Page  int    `json:"page" validate:"required"`  
    Limit int    `json:"limit" validate:"required"`  
    Order string `json:"order"`  
    Desc  bool   `json:"desc"`  
}

type IDo[T any] interface {  
    Limit(_ int) T  
    Order(_ ...field.Expr) T  
    Offset(_ int) T  
}  
  
// DoQuery 根据查询条件构造查询  
func DoQuery[T IDo[T]](query QueryBaseReq, do T, model interface {GetFieldByName(fieldName string) (field.OrderExpr, bool)}) (T, error) {  
    order, ok := model.GetFieldByName(query.Order)  
    if ok {  
       if query.Desc {  
          do = do.Order(order.Desc())  
       } else {  
          do = do.Order(order.Asc())  
       }  
    } else {  
       return do, errors.New("排序字段不存在")  
    }  
    do = do.Limit(query.Limit).Offset(query.Page * query.Limit)  
    return do, nil  
}
许可协议:  GPL V3