golang没有泛型

golang是一门简单易用的语言,没有泛型,导致有些代码写起来比较啰嗦,像一些序列化反序列化方面的需求,对不同类型的操作都要分别实现,代码冗余度较高.

比如说这样一个需求:

数据结构对象的存储和读取

1
2
3
4
5
6
7
8
type A struct {
	t int `json:"tt"`
}

type B struct {
	tt int `json:"tt"`
	xx int `json:"xx"`
}

redis里分别存入了AB的若干对象的json序列化字符串,现在要取出所有的A类型对象和所有的B类型对象

取出A类型对象的方法

1
GetA() (output []A, err error)

取出B类型对象的方法

1
GetB() (output []B, err error)

相似的逻辑要分别写两遍.

golang反射

那么针对这样的需求,只能一遍又一遍的去实现逻辑近似的函数吗?可以看看一些标准的序列化反序列化库是怎么做的,典型的像json序列化反序列化.

序列化接口:

1
func Marshal(v interface{}) ([]byte, error)

反序列化接口:

1
func Unmarshal(data []byte, v interface{}) error

可以看到这里使用interface{}这个类型作为参数,代表任意类型,这就是golang解决这类问题的方法: interface{}配合golang的反射.

回头来看上面的需求,把[]A[]B使用interface{}替代,不要使用interface{}作为返回值,否则使用接口的时候又要做一次类型转换,复杂度仍然是带到了使用方. 参考json反序列化的接口,将返回值作为一个inout参数.

先来看一个初始版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func Get(out interface{}, outhint interface{}) {
	v := reflect.ValueOf(out).Elem()
	v.Set(reflect.MakeSlice(v.Type(), 2, 2))
	for i, w := range []string{"{\"tt\":1}", "{\"tt\":2}"} {
		json.Unmarshal([]byte(w), outhint)
		fmt.Println("out: ", outhint)
		v.Index(i).Set(reflect.ValueOf(outhint).Elem())
	}
	fmt.Println(v)
}

这个版本比较简单,其中out参数表示结果slice,outhint参数表示slice内的元素类型,这个参数降低了代码复杂度.

可以看到函数实现中的直接使用outhint作为jsonUnmarshal(),reflectValueOf()的参数.

实际使用时outhint就是out这个slice内的元素的类型, outhintout显然是有关联的,outhint这个参数是可以省略的.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func GetSlice(out interface{}) {
    v := reflect.ValueOf(out).Elem()
    v.Set(reflect.MakeSlice(v.Type(), 2, 2))
    vtype := reflect.TypeOf(out).Elem()
    vele := vtype.Elem()
    fmt.Println("vele type: ", vele.Name())
    if vele.Kind() == reflect.Ptr {
        fmt.Println("element is pointer type")
		//模拟从redis里遍历符合条件的元素
        for i, w := range []string{"{\"tt\":1}", "{\"tt\":2}"} {
            elev := reflect.New(vele.Elem())
            tmp := elev.Interface()
            json.Unmarshal([]byte(w), tmp)
            //  fmt.Println("76 out: ", tmp)
            v.Index(i).Set(elev)
        }
    } else {
        fmt.Println("element is struct type")
		
        for i, w := range []string{"{\"tt\":1}", "{\"tt\":2}"} {
            elev := reflect.New(vele)
            tmp := elev.Interface()
            json.Unmarshal([]byte(w), tmp)
            //  fmt.Println("76 out: ", tmp)
            v.Index(i).Set(elev.Elem())
        }

    }
}

上面这个函数使用interface{}和反射实现了泛型需求.可以看到,其中类型判断还是不太安全的,写法也比较复杂

golang1.18将会加入泛型,到时候对于这类需求将会有更好的实现方式.