golang/go语言泛型的使用

前言

  泛型 是什么? 其实这个问题对于学过 C++ 的同学来说不陌生就是模版嘛, 在运行时才确定类型嘛. 确实这两句话就基本能解析清楚, 但像一些只接触过 Python 的同学来说确实没听说过, 毕竟就是 Python 是动态编译的不需要泛型.

  对于 Golang 来说, 泛型在牺牲一点性能的前提下能大大降低对通用代码的编码难度, 不然只能乖乖的用反射解决问题, 或者对不同类型多复制出几份代码;

  本文没有对泛型进行入门介绍, 需要的请跳转 泛型初识.

泛型实践

关键字参数

  • 众所周知很多语言的function 中都支持 key=word 关键字参数, 但 golang 是不支持的, 我们可以利用泛型去简单的实现.
func DefaultKeyWordParams[D any](defVal D, params ...D) D {
        if len(params) == 0 {
                return defVal
        }
        return params[0]
}

func test(category ...string) {
    // 不填写则返回默认值
    realCategory := DefaultKeyWordParams[string]("AGroup", category...)
    fmt.Println(realCategory)
}

func main () {
     test()
}

快速排序

UpdateAt: 2023-02-22

  • 实现一个可进行控制反转的通用类型快速排序, 解决一下原生的sort包进行类型定义的繁琐.
// QuickSort 通用快速排序
func QuickSort[T any](arr []T, compareFn func(a, b T) bool) {
        if len(arr) < 2 {
                return
        }

        pivot := arr[0]
        left := 1
        right := len(arr) - 1

        for left <= right {
                if compareFn(arr[left], pivot) {
                        left++
                } else if compareFn(pivot, arr[right]) {
                        right--
                } else {
                        arr[left], arr[right] = arr[right], arr[left]
                }
        }

        arr[0], arr[right] = arr[right], arr[0]

        QuickSort(arr[:right], compareFn)
        QuickSort(arr[right+1:], compareFn)
}
  • 测试用例
func TestQuickSort(t *testing.T) {
        nums := []int{9, 3, 1, 7, 4, 8, 6, 2, 5}
        fmt.Println("Unsorted:", nums)

        QuickSort[int](nums, func(a, b int) bool {
                return a < b
        })
        fmt.Println("Sorted:  ", nums)

        strs := []string{"orange", "apple", "banana", "kiwi", "grape"}
        fmt.Println("Unsorted:", strs)

        QuickSort[string](strs, func(a, b string) bool {
                return len(a) < len(b)
        })
        fmt.Println("Sorted:  ", strs)
}

去重复

  • 这是一个简单的实现, 复杂点可以通过回调 + 泛型来实现;
func RemoveDuplicate[T string | int | float64](duplicateSlice []T) []T {
        set := map[T]interface{}{}
        res := []T{}
        for _, item := range duplicateSlice {
                _, ok := set[item]
                if !ok {
                        res = append(res, item)
                        set[item] = nil
                }
        }
        return res
}

func main() {
        fmt.Println(RemoveDuplicate[string]([]string{"a", "c", "a"}))
        fmt.Println(RemoveDuplicate[int]([]int{1, 2, 1, 1, 1}))
}
  • 通过控制反转实现通用的去重复方法, 支持任意类型;
type Student struct {
        Name string
        Age  int
}

func NewStudent(name string, age int) *Student {
        return &Student{Name: name, Age: age}
}

func DefaultFilter(item interface{}) (uniqueKey interface{}) {
        return item.(*Student).Name
}

func RemoveDuplicateWithFilter[T comparable](compareSlice []T, filterFunc func(item interface{}) (key interface{})) []T {
        set := map[interface{}]interface{}{}
        res := []T{}
        for _, item := range compareSlice {
                i := filterFunc(item)
                _, ok := set[i]
                if !ok {
                        res = append(res, item)
                        set[i] = nil
                }
        }
        return res
}

func main() {
        s := []*Student{
                NewStudent("a", 1),
                NewStudent("a", 1),
                NewStudent("b", 2),
                NewStudent("b", 2),
        }
        l := RemoveDuplicateWithFilter[*Student](s, DefaultFilter)
        for _, i := range l {
                fmt.Println(i.Name, i.Age)
        }
}

联合约束类型

  • 该例子只是一个演示, 没有实际效果
type ID interface {
    int | string
}

// 写法  [T ID, D string] == [T int | string, D string]
type UserModel[T ID, D string] struct {
        Id   T
        Name D
}

func NewUserModel[A ID, D string](id A, name D) *UserModel[A, D] {
        return &UserModel[A, D]{Id: id, Name: name}
}

func main() {
        fmt.Println(NewUserModel[int, string](10, "hello"))
        fmt.Println(NewUserModel[string, string]("10", "hello"))
}

分页

  • 这是一段线上在使用的分页代码, 当无法使用外部存储器进行分页时直接使用该对象进行分页, 支持任意类型;
type KeepItem bool

// 若需要保留的item 则返回true 即可
type FilterFunc func(item interface{}) KeepItem

type PageList[T any] struct {
        Total int `json:"total"`
        Page  int `json:"page"`
        Size  int `json:"size"`
        List  []T `json:"list"`
}

type Pager[T any] struct {
        limit   int
        offset  int
        total   int
        pageCnt int
        list    []T
}

func NewPager[T any](list []T) *Pager[T] {
        return &Pager[T]{
                limit:  10,
                offset: 1,
                total:  len(list),
                list:   list,
        }
}

func (this *Pager[T]) Filter(filterFn FilterFunc) *Pager[T] {
        tmpList := []T{}
        for _, item := range this.list {
                if filterFn(&item) {
                        tmpList = append(tmpList, item)
                }
        }
        this.list = tmpList
        this.total = len(tmpList)
        return this
}

func (this *Pager[T]) Offset(c int) *Pager[T] {
        this.offset = c
        return this
}

func (this *Pager[T]) Limit(c int) *Pager[T] {
        this.limit = c
        return this
}

func (this *Pager[T]) List() []T {
        // 页码
        if this.offset <= 0 {
                this.offset = 1
        }
        // size
        if this.limit > this.total {
                this.limit = this.total
        }
        // 总页数
        this.pageCnt = int(math.Ceil(float64(this.total) / float64(this.limit)))
        if this.offset > this.pageCnt {
                return []T{}
        }
        startIdx := (this.offset - 1) * this.limit
        endIdx := startIdx + this.limit

        if endIdx > this.total {
                endIdx = this.total
        }

        return this.list[startIdx:endIdx]
}

func (this *Pager[T]) Output() *PageList[T] {

        return &PageList[T]{
                Total: this.total,
                Page:  this.offset,
                Size:  this.limit,
                List:  this.list,
        }
}

// test
func main () {
    page := NewPager[int]([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
        list := page.Offset(1).Limit(3).Filter(func(item interface{}) KeepItem {
    if *item.(*int)%2 == 1 {
        return true
    }
    return false
        }).List()
        fmt.Println(list)
}

通用初始化模型

  • 可以解决在多态下使用同一个初始化函数进行对象初始化, 写法上有点绕大家自行多实验几次就能明白.
type ModelObj interface {
        User | Product
}

type User struct {
        Uid int
}

func (this *User) SetId(id int) {
        this.Uid = id
}

type Product struct {
        Pid int
}

func (this *Product) SetId(id int) {
        this.Pid = id
}

// TrimModelObj 是一个动态类型的 Interface, 由M决定当前Interface的最终类型
type TrimModelObj[M ModelObj] interface {
        *M
        SetId(id int)
}

// TrimModelObj[Model] 由第二个参数决定当前的动态类型;
// NewModelObj[*User, User](32) 如 Model 是 User 类型, 最终 TrimModelObj == *User,所以我们需要为 Trim 传递 *User
func NewModelObj[Trim TrimModelObj[Model], Model ModelObj](id int) Trim {
        m := new(Model)
        t := Trim(m)
        fmt.Printf("%p n", m)
        // 类型转换成指定的*Model
        t.SetId(id)
        return t
}

func main() {
        // new user model object
        user := NewModelObj[*User, User](32)
        fmt.Printf("%p n", user)
        fmt.Printf("%T n", user)
        fmt.Println(user.Uid)

        // new product model object
        prod := NewModelObj[*Product, Product](18)
        fmt.Printf("%p n", prod)
        fmt.Printf("%T n", prod)
        fmt.Println(prod.Pid)
}

一堆类型转换 Convertor

  • UpdateAt: 2023-03-20
package convertor

import (
        "encoding/json"
        "fmt"
)

// InterfaceToSlice interface类型转slice
func InterfaceToSlice[T any](i interface{}) ([]T, error) {
        s := make([]T, 0)
        marshal, err := json.Marshal(i)
        if err != nil {
                return nil, fmt.Errorf("convert iterface to slice error. %s", err.Error())
        }
        if err := json.Unmarshal(marshal, &s); err != nil {
                return nil, fmt.Errorf("convert iterface to slice error. %s", err.Error())
        }
        return s, nil
}

// InterfaceToStruct interface  类型转 struct
func InterfaceToStruct[T any](i interface{}) (*T, error) {
        marshal, err := json.Marshal(i)
        if err != nil {
                return nil, fmt.Errorf("convert interface to struct error. %s", err.Error())
        }

        t := new(T)
        if err := json.Unmarshal(marshal, t); err != nil {
                return nil, fmt.Errorf("convert interface to struct error. %s", err.Error())
        }
        return t, nil
}

// InterfaceToMap interface 转 map
func InterfaceToMap[K comparable, V any](i interface{}) (map[K]V, error) {
        marshal, err := json.Marshal(i)
        if err != nil {
                return nil, fmt.Errorf("convert interface to map error. %s", err.Error())
        }
        m := make(map[K]V, 0)
        if err := json.Unmarshal(marshal, &m); err != nil {
                return nil, fmt.Errorf("convert interface to map error. %s", err.Error())
        }
        return m, nil
}

// MapToStruct map to struct
func MapToStruct[T any](m map[string]interface{}) (*T, error) {
        t := new(T)
        marshal, err := json.Marshal(m)
        if err != nil {
                return nil, fmt.Errorf("convert map to struct error. %s", err.Error())
        }
        if err := json.Unmarshal(marshal, &t); err != nil {
                return nil, fmt.Errorf("convert map to struct error. %s", err.Error())
        }
        return t, nil

}

// StructToMap struct to map
func StructToMap[K comparable, V any](s interface{}) (map[K]V, error) {
        m := make(map[K]V, 0)
        marshal, err := json.Marshal(s)
        if err != nil {
                return nil, fmt.Errorf("convert struct to map error. %s", err.Error())
        }
        if err := json.Unmarshal(marshal, &m); err != nil {
                return nil, fmt.Errorf("convert struct to map error. %s", err.Error())
        }
        return m, nil
}