这篇文章主要为大家分析了如何在go语言中利用反射精简代码的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟随小编一起来看看,下面跟着小编一起深入学习“如何在go语言中利用反射精简代码”的知识吧。
反射是 Go 语言中非常重要的一个知识点。反射是设计优雅程序的法宝,orm json 序列化,参数校验都离不开它,我们今天以一个业务开发中的实例,来简单讲解下反射在日常开发中的用处。
相信大家在使用 go 编写业务代码的时候都会写过这样的代码,这类代码的本质是,将一个集合中的数据按照名称绑定到结构体的对应属性上去,集合的类型不限于 map slice struct 甚至可以是 interface[^interface],
[^interface]: 这个就比较 trick 了
type TestValue struct { IntValue int StringValue string IntArray []int StringArray []string } dataMap := map[string]string{ "int_value":"1", "string_value":"str", "int_array":"[1,2,3]", "string_array":"[\"1\",\"2\",\"3\"]", } config := TestValue{} if value, ok := dataMap["int_value"]; ok { config.IntValue, _ = datautil.TransToInt64(value) } if value, ok := dataMap["string_value"]; ok { config.StringValue = value } if value, ok := dataMap["int_array"]; ok { config.IntArray = stringToIntArray(value) } if value, ok := dataMap["string_array"]; ok { config.StringArray = stringToStrArray(value) } return config
这部分代码中最挫的地方就是结构体赋值的时候一个一个进行的复制,若整个结构体非常大,赋值的代码可能会写满满一屏,bug出现的几率也就大大增加,我们的目的就是通过反射来简化赋值的步骤,通过一个方法将集合中的数据绑定到结构体上
反射简述
要做到这一步,我们首先了解下,在 go 语言中,我们的变量是由什么组成的
_type 类型信息
*data 指向实际值的指针
itab 接口方法
图上第一个 type 是一个反射类型对象,表示了变量类型的一些信息,第二个表示结构体属性对应的的 type,包含了结构体属性的一些信息
reflect.Type : /go/src/reflect/value.go:36
bind function
看到这张图我们大概就明白应该怎样做了,目标是编写一个绑定方法,必须建立一个绑定关系,把这个结构体加上一个 tag ,通过 tag 和 map 中的数据建立关联
// 创建一个具有深刻含义的 tag type TestValue struct { IntValue int `qiudianzan:"int_value"` StringValue string `qiudianzan:"string_value"` IntArray []int `qiudianzan:"int_array"` StringArray []string `qiudianzan:"string_array"` }
紧接着获取结构体的 tag ,通过反射轻而易举的做到了
func bind(configMap map[string]string, result interface{}) error { v := reflect.ValueOf(result).Elem() t := v.Type() for i := 0; i < t.NumField(); i++ { tag := t.Field(i).Tag.Get("qiudianzan") fmt.Println(tag) }
绑定关系完成以后,就需要向结构体写入 map 中的值,这时候问题来了,map 中的数据结构都是 string ,但是结构体属性类型五花八门,并不能直接将 map 中的数据写入,还需要根据结构体属性类型做一步类型转换
v.Field(i).SetInt(res) v.Field(i).SetUint(res) v.Field(i).SetFloat(res) . . .
通过反射可以获取属性的两种表示类型
的反射对象
reflect.Type // 静态类型 reflect.Kind // 底层数据的类型
我们通过下面的例子来确定使用哪一个
type A struct { } func main() { var a A kinda := reflect.ValueOf(a).Kind() typea := reflect.TypeOf(a) fmt.Println(kinda) fmt.Println(typea) }struct main.A
变量 a 的静态类型为 A,但是 a 的底层数据类型则为 struct,所以我们想根据类型
解析,这里说的类型
是指的 reflect.Kind
通过底层数据类型来转换 map 中的数据
switch v.Field(i).Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: res, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } v.Field(i).SetInt(res) case reflect.String: v.Field(i).SetString(value) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: res, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } v.Field(i).SetUint(res)
再稍稍整理下添加一点细节,一个简单的绑定函数就大功告成了
func bind(configMap map[string]string, result interface{}) error { // 被绑定的结构体非指针错误返回 if reflect.ValueOf(result).Kind() != reflect.Ptr { return errors.New("input not point") } // 被绑定的结构体指针为 null 错误返回 if reflect.ValueOf(result).IsNil() { return errors.New("input is null") } v := reflect.ValueOf(result).Elem() t := v.Type() for i := 0; i < t.NumField(); i++ { tag := t.Field(i).Tag.Get("json") // map 中没该变量有则跳过 value, ok := configMap[tag] if !ok { continue } // 跳过结构体中不可 set 的私有变量 if !v.Field(i).CanSet() { continue } switch v.Field(i).Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: res, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } v.Field(i).SetInt(res) case reflect.String: v.Field(i).SetString(value) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: res, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } v.Field(i).SetUint(res) case reflect.Float32: res, err := strconv.ParseFloat(value, 32) if err != nil { return err } v.Field(i).SetFloat(res) case reflect.Float64: res, err := strconv.ParseFloat(value, 64) if err != nil { return err } v.Field(i).SetFloat(res) case reflect.Slice: var strArray []string var valArray []reflect.Value var valArr reflect.Value elemKind := t.Field(i).Type.Elem().Kind() elemType := t.Field(i).Type.Elem() value = strings.Trim(strings.Trim(value, "["), "]") strArray = strings.Split(value, ",") switch elemKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: for _, e := range strArray { ee, err := strconv.ParseInt(e, 10, 64) if err != nil { return err } valArray = append(valArray, reflect.ValueOf(ee).Convert(elemType)) } case reflect.String: for _, e := range strArray { valArray = append(valArray, reflect.ValueOf(strings.Trim(e, "\"")).Convert(elemType)) } } valArr = reflect.Append(v.Field(i), valArray...) v.Field(i).Set(valArr) } } return nil}
之前的看起来非常难看的代码瞬间就变得很简单
type TestValue struct { IntValue int StringValue string IntArray []int StringArray []string } dataMap := map[string]string{ "int_value":"1", "string_value":"str", "int_array":"[1,2,3]", "string_array":"[\"1\",\"2\",\"3\"]", } config := TestValue{} err := bind(dataMap,&config)
在这里只是提供一种绑定的思路,其实在实际开发中,遇到结构体值绑定/校验/格式化/方法绑定,都可以使用类似的思路,避免 one by one 的编写代码。
关于“如何在go语言中利用反射精简代码”就介绍到这了,更多相关内容可以搜索编程网以前的文章,希望能够帮助大家答疑解惑,请多多支持编程网网站!