当前位置:网站首页>[Golang]用反射让你的代码变优美
[Golang]用反射让你的代码变优美
2022-08-10 20:30:00 【用户9959267】
反射是 Go 语言中非常重要的一个知识点。反射是设计优雅程序的法宝,orm json 序列化,参数校验都离不开它,我们今天以一个业务开发中的实例,来简单讲解下反射在日常开发中的用处。
场景
相信大家在使用 go 编写业务代码的时候都会写过这样的代码,这类代码的本质是,将一个集合中的数据按照名称绑定到结构体的对应属性上去,集合的类型不限于 map slice struct 甚至可以是 interface。
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 接口方法
reflect.Type :/go/src/reflect/value.go:36
图上第一个 type 是一个反射类型对象,表示了变量类型的一些信息,第二个表示结构体属性对应的的 type,包含了结构体属性的一些信息。
看到这张图我们大概就明白应该怎样做了,目标是编写一个绑定方法,必须建立一个绑定关系,把这个结构体加上一个 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)
}
// res:
// 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
}
有了bind函数,之前的看起来非常难看的代码瞬间就变得很简单了。
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 的编写代码。 这是使用这种思路的一些开源工具:
- 结构体参数校验 gopkg.in/go-playground/validator.v9
- 格式化 json.Unmarshal
- orm工具 github.com/jmoiron/sqlx/ 将结构体属性转换成 pre sql
边栏推荐
猜你喜欢
(10) Sequence and deserialization of image data
【一致性hash】负载均衡器分发请求
leetcode 84.柱状图中最大的矩形 单调栈应用
idea插件 协议 。。 公司申请软件用
【图像分类】2018-MobileNetV2
【语义分割】2016-SegNet TPAMI
转铁蛋白(Tf)修饰去氢骆驼蓬碱磁纳米脂质体/香豆素-6脂质体/多柔比星脂质体
ansible各个模块的详解和使用
Transferrin-modified vincristine-tetrandrine liposomes | transferrin-modified co-loaded paclitaxel and genistein liposomes (reagents)
Are you hungry - Institution tree radio
随机推荐
设备管理中数据聚类处理
重载和重写
Go程序员进化史
(十二)STM32——NVIC中断优先级管理
C语言详解系列——关于调试那些事
The most complete GIS related software in history (CAD, FME, ArcGIS, ArcGISPro)
2019河北省大学生程序设计竞赛部分题题解
Rider调试ASP.NET Core时报thread not gc-safe的解决方法
【语义分割】2016-SegNet TPAMI
cordova installation error Command failed: powershell solution
[SWPUCTF 2021 新生赛] web
[mysql] 深入分析MySQL版本控制MVCC规则
UE4 - 河流流体插件Fluid Flux
七月券商金工精选
"Distributed Microservice E-commerce" Topic (1) - Project Introduction
"POJ 3666" Making the Grade problem solution (two methods)
如何提交一个PR?【OpenHarmony成长计划】【OpenHarmony开源社区】
姜还是老的辣,看看老战哥的老底儿和严谨劲儿
血红素-金纳米颗粒(Heme-AuNP)复合纳米酶|金纳米颗粒核多孔空心碳纳米球壳([email protected])纳米酶
Water-soluble alloy quantum dot nanozymes|CuMoS nanozymes|porous silicon-based Pt(Au) nanozymes|[email protected] nanomimetic e