Go 泛型实战
❝
可以遗憾,但不要后悔。
我们留在这里,从来不是身不由己。
——— 而是选择在这里经历生活
❞
在 Go 编程中,泛型是一个备受期待的特性,它为开发者提供了更大的灵活性和重用性。本文将探讨如何在 Go 中利用泛型解决多种问题,包括实体类型转换、工厂模式以及 JSON 反序列化。通过这些例子,我们将窥探泛型在 Go 中的实际用途和优势。
实体类型转换
仅作为演示 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()
}