Go 泛型代码实战

561 阅读3分钟

Go 泛型实战

可以遗憾,但不要后悔。 

我们留在这里,从来不是身不由己。 

——— 而是选择在这里经历生活

Go 编程中,泛型是一个备受期待的特性,它为开发者提供了更大的灵活性和重用性。本文将探讨如何在 Go 中利用泛型解决多种问题,包括实体类型转换、工厂模式以及 JSON 反序列化。通过这些例子,我们将窥探泛型在 Go 中的实际用途和优势。

image.png

实体类型转换

仅作为演示 Generic 带来的便利性,代码示例随便写的,并不建议使用在实际项目中。

普通写法

使用 GetUserModelToVO 函数将 UserModel 数据库中的用户模型转换为传递给前端的 UserVO 用户视图对象。

注意到按照传统的方式,仅仅是一个字段的数据类型不同,而需要重复写两遍代码。

type UserVO struct {
    UserID   int    `json:"user_id"`
    UserName string `json:"user_name"`
}

type UserModel struct {
    UserID   uint64 `gorm:"column:user_id"`
    UserName string `gorm:"column:user_name"`
}

func GetUserModelToVO(ms []UserModel) []UserVO {
    vs := make([]UserVO, 0, len(ms))
    for _, v := range ms {
        vs = append(vs, UserVO{
            UserID:   int(v.UserID),
            UserName: v.UserName,
        })
    }
    return vs
}

func main() {
    userModels := []UserModel{
        {UserID: 1, UserName: "John"},
        {UserID: 2, UserName: "Alice"},
        {UserID: 3, UserName: "Bob"},
    }

    userVOs := GetUserModelToVO(userModels)

    bs, _ := json.Marshal(userVOs)

    fmt.Println(string(bs))
}

泛型写法

这样的泛型写法,极大的简化了实体模型重复性定义,能够使开发者更专注于业务逻辑的实现。

type Number interface {
    int | int8 | int16 | int32 | int64 |
        uint | uint8 | uint16 | uint32 | uint64 |
        float32 | float64
}

type User[T Number] struct {
    UserID   T      `gorm:"column:user_id"   json:"user_id"`
    UserName string `gorm:"column:user_name" json:"user_name"`
}

func UserModelsToVO[in Number, out Number](ms []User[in]) []User[out] {
    vs := make([]User[out], 0, len(ms))
    for _, v := range ms {
        vs = append(vs, User[out]{
            UserID:   out(v.UserID),
            UserName: v.UserName,
        })
    }
    return vs
}

func main() {
    userModels := []User[uint64]{
        {UserID: 1, UserName: "John"},
        {UserID: 2, UserName: "Alice"},
        {UserID: 3, UserName: "Bob"},
    }

    userVOs := UserModelsToVO[uint64, int](userModels)

    bs, _ := json.Marshal(userVOs)

    fmt.Println(string(bs))
}

工厂模式

普通写法

type Vegetable struct {
    VegetableName string
}
type Fruit struct {
    FruitName string
}
type Toy struct {
    ToyName string
}

func NewVegetable() *Vegetable {
    return &Vegetable{}
}
func NewFruit() *Fruit {
    return &Fruit{}
}
func NewToy() *Toy {
    return &Toy{}
}

泛型写法

type Vegetable struct {
    VegetableName string
}

type Fruit struct {
    FruitName string
}

type Toy struct {
    ToyName string
}

type Food interface {
    Vegetable | Fruit
}

func New[T Food]() *T {
    var obj T
    return &obj
}

func main() {
    v := New[Vegetable]()
    v.VegetableName = "cabbage"
    fmt.Printf("%T, %v\n", v, v) // Output: *main.Vegetable, &{cabbage}

    f := New[Fruit]()
    f.FruitName = "apple"
    fmt.Printf("%T, %v\n", f, f) // Output: *main.Fruit, &{apple}

    m := New[Toy]()              // Output: Toy does not satisfy Food (Toy missing in main.Vegetable | main.Fruit)
    fmt.Printf("%T\n", m)
}

JSON 反序列化

限定下场景,如果使用 map 来解析 json,使用传统方式解析是很痛苦的!而通过 Generic 在一定程度上能简化代码。当然更多时候我们是不会使用 map 来解析 json 的,这里只是作为泛型的练习。

普通写法

var (
    apiObj = `
{
    "name": "John",
    "age": 21,
    "hobbies": [
        "swimming",
        "singing"
    ]
}
`

    apiList = `
[
    {
        "name": "John",
        "age": 21
    },
    {
        "name": "Jane",
        "age": 26
    }
]
`
)

func ParseJson(s string) (interface{}, error) {
    var v interface{}
    if err := json.Unmarshal([]byte(s), &v); err != nil {
        return nil, err
    }
    return v, nil
}

func RetApiObj() {
    result, err := ParseJson(apiObj)
    if err != nil {
        panic(err)
    }
    if item, ok := result.(map[string]interface{}); ok {
        if v, ok := item["hobbies"].([]interface{}); ok {
            fmt.Println(v[0])
        }
    }
    // Output:
    // swimming
}

func RetApiList() {
    result, err := ParseJson(apiList)
    if err != nil {
        panic(err)
    }
    if item, ok := result.([]interface{}); ok {
        if v, ok := item[0].(map[string]interface{}); ok {
            fmt.Println(v["name"])
        }
    }
    // Output:
    // John
}

func main() {
    RetApiObj()
    RetApiList()
}

泛型写法

var (
    apiObj = `
{
    "name": "John",
    "age": 21,
    "hobbies": [
        "swimming",
        "singing"
    ]
}
`

    apiList = `
[
    {
        "name": "John",
        "age": 21
    },
    {
        "name": "Jane",
        "age": 26
    }
]
`
)

type JsonObj interface {
    []any | []map[string]any | map[string]any
}

func DecodeJson[T JsonObj](s string) (T, error) {
    var v T
    if err := json.Unmarshal([]byte(s), &v); err != nil {
        return nil, err
    }
    return v, nil
}

func RetApiObj() {
    res1, err := DecodeJson[map[string]any](apiObj)
    if err != nil {
        panic(err)
    }
    fmt.Println(res1["hobbies"].([]any)[0])
    // Output:
    // swimming
}

func RetApiList() {
    res2, err := DecodeJson[[]map[string]any](apiList)
    if err != nil {
        panic(err)
    }
    fmt.Println(res2[0]["name"])
    // Output:
    // John
}

func main() {
    RetApiObj()
    RetApiList()
}