如何在go语言中利用反射精简代码
如何在go语言中利用反射精简代码
这篇文章主要为大家分析了如何在go语言中利用反射精简代码的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟随小编一起来看看,下面跟着小编一起深入学习“如何在go语言中利用反射精简代码”的知识吧。
反射是 Go 语言中非常重要的一个知识点。反射是设计优雅程序的法宝,orm json 序列化,参数校验都离不开它,我们今天以一个业务开发中的实例,来简单讲解下反射在日常开发中的用处。
相信大家在使用 go 编写业务代码的时候都会写过这样的代码,这类代码的本质是,将一个集合中的数据按照名称绑定到结构体的对应属性上去,集合的类型不限于 map slice struct 甚至可以是 interface[^interface],
[^interface]: 这个就比较 trick 了
typeTestValuestruct{IntValueintStringValuestringIntArray[]intStringArray[]string}dataMap:=map[string]string{"int_value":"1","string_value":"str","int_array":"[1,2,3]","string_array":"[\"1\",\"2\",\"3\"]",}config:=TestValue{}ifvalue,ok:=dataMap["int_value"];ok{config.IntValue,_=datautil.TransToInt64(value)}ifvalue,ok:=dataMap["string_value"];ok{config.StringValue=value}ifvalue,ok:=dataMap["int_array"];ok{config.IntArray=stringToIntArray(value)}ifvalue,ok:=dataMap["string_array"];ok{config.StringArray=stringToStrArray(value)}returnconfig
这部分代码中最挫的地方就是结构体赋值的时候一个一个进行的复制,若整个结构体非常大,赋值的代码可能会写满满一屏,bug出现的几率也就大大增加,我们的目的就是通过反射来简化赋值的步骤,通过一个方法将集合中的数据绑定到结构体上
反射简述
要做到这一步,我们首先了解下,在 go 语言中,我们的变量是由什么组成的
_type 类型信息
*data 指向实际值的指针
itab 接口方法
图上第一个 type 是一个反射类型对象,表示了变量类型的一些信息,第二个表示结构体属性对应的的 type,包含了结构体属性的一些信息
reflect.Type : /go/src/reflect/value.go:36
bind function
看到这张图我们大概就明白应该怎样做了,目标是编写一个绑定方法,必须建立一个绑定关系,把这个结构体加上一个 tag ,通过 tag 和 map 中的数据建立关联
//创建一个具有深刻含义的tagtypeTestValuestruct{IntValueint`qiudianzan:"int_value"`StringValuestring`qiudianzan:"string_value"`IntArray[]int`qiudianzan:"int_array"`StringArray[]string`qiudianzan:"string_array"`}
紧接着获取结构体的 tag ,通过反射轻而易举的做到了
funcbind(configMapmap[string]string,resultinterface{})error{v:=reflect.ValueOf(result).Elem()t:=v.Type()fori:=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//底层数据的类型
我们通过下面的例子来确定使用哪一个
typeAstruct{}funcmain(){varaAkinda:=reflect.ValueOf(a).Kind()typea:=reflect.TypeOf(a)fmt.Println(kinda)fmt.Println(typea)}structmain.A
变量 a 的静态类型为 A,但是 a 的底层数据类型则为 struct,所以我们想根据类型
解析,这里说的类型
是指的reflect.Kind
通过底层数据类型来转换 map 中的数据
switchv.Field(i).Kind(){casereflect.Int,reflect.Int8,reflect.Int16,reflect.Int32,reflect.Int64:res,err:=strconv.ParseInt(value,10,64)iferr!=nil{returnerr}v.Field(i).SetInt(res)casereflect.String:v.Field(i).SetString(value)casereflect.Uint,reflect.Uint8,reflect.Uint16,reflect.Uint32,reflect.Uint64:res,err:=strconv.ParseUint(value,10,64)iferr!=nil{returnerr}v.Field(i).SetUint(res)
再稍稍整理下添加一点细节,一个简单的绑定函数就大功告成了
funcbind(configMapmap[string]string,resultinterface{})error{//被绑定的结构体非指针错误返回ifreflect.ValueOf(result).Kind()!=reflect.Ptr{returnerrors.New("inputnotpoint")}//被绑定的结构体指针为null错误返回ifreflect.ValueOf(result).IsNil(){returnerrors.New("inputisnull")}v:=reflect.ValueOf(result).Elem()t:=v.Type()fori:=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}switchv.Field(i).Kind(){casereflect.Int,reflect.Int8,reflect.Int16,reflect.Int32,reflect.Int64:res,err:=strconv.ParseInt(value,10,64)iferr!=nil{returnerr}v.Field(i).SetInt(res)casereflect.String:v.Field(i).SetString(value)casereflect.Uint,reflect.Uint8,reflect.Uint16,reflect.Uint32,reflect.Uint64:res,err:=strconv.ParseUint(value,10,64)iferr!=nil{returnerr}v.Field(i).SetUint(res)casereflect.Float32:res,err:=strconv.ParseFloat(value,32)iferr!=nil{returnerr}v.Field(i).SetFloat(res)casereflect.Float64:res,err:=strconv.ParseFloat(value,64)iferr!=nil{returnerr}v.Field(i).SetFloat(res)casereflect.Slice:varstrArray[]stringvarvalArray[]reflect.ValuevarvalArrreflect.ValueelemKind:=t.Field(i).Type.Elem().Kind()elemType:=t.Field(i).Type.Elem()value=strings.Trim(strings.Trim(value,"["),"]")strArray=strings.Split(value,",")switchelemKind{casereflect.Int,reflect.Int8,reflect.Int16,reflect.Int32,reflect.Int64:for_,e:=rangestrArray{ee,err:=strconv.ParseInt(e,10,64)iferr!=nil{returnerr}valArray=append(valArray,reflect.ValueOf(ee).Convert(elemType))}casereflect.String:for_,e:=rangestrArray{valArray=append(valArray,reflect.ValueOf(strings.Trim(e,"\"")).Convert(elemType))}}valArr=reflect.Append(v.Field(i),valArray...)v.Field(i).Set(valArr)}}returnnil}
之前的看起来非常难看的代码瞬间就变得很简单
typeTestValuestruct{IntValueintStringValuestringIntArray[]intStringArray[]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语言中利用反射精简代码”就介绍到这了,更多相关内容可以搜索恰卡编程网以前的文章,希望能够帮助大家答疑解惑,请多多支持恰卡编程网网站!
推荐阅读
-
go如何实现职责链模式
-
Go Callvis如何使用
-
go pprof如何使用
-
Go框架三件套Gorm、Kitex、Hertz怎么使用
-
Go语言单元测试和基准测试实例代码分析
-
go Realize怎么使用
goRealize怎么使用本文小编为大家详细介绍“goReal...
-
14天学会Go语言第四天:Array Map Slice 数组 和切片
-
如何从 PHP 过渡到 Golang?
我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗...
-
零经验Go语言MVC框架,并推荐PHP函数库和MySQL表自动转struct
最近搭建k8s系统,为了调试自动部署用Go做了个MVC框架,这是个适用于喜欢Go语言练习的框架,零Go语言经验学习Go;配合Go模...
-
从php到Golang系统的演变