在 go 語言中利用反射精簡程式碼
反射是 Go 語言中非常重要的一個知識點。反射是設計優雅程式的法寶,orm json 序列化,引數校驗都離不開它,我們今天以一個業務開發中的例項,來簡單講解下反射在日常開發中的用處。
本文使用的 case 皆為專案開發中的例項,為了脫敏簡化了程式碼
相信大家在使用 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 的編寫程式碼
這是使用這種思路的一些開源工具
- 結構體引數校驗 http:// gopkg.in/go-playground/ validator.v9
- 格式化 json.Unmarshal
- orm工具 http:// github.com/jmoiron/sqlx / 將結構體屬性轉換成 pre sql
作者:許睿
為研發提效,全是技術乾貨的滴滴雲技術沙龍報名中!
馬上關注滴滴雲公眾號:
回覆「上課」獲取免費報名資格
回覆「伺服器」免費獲得雲伺服器入門1個月體驗
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559758/viewspace-2688524/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Go 語言中常見的幾種反模式Go模式
- hash 表在 go 語言中的實現Go
- 在 Go 語言中,我為什麼使用介面Go
- 為什麼在Go語言中要慎用interface{}Go
- 在Go語言中使用 Protobuf-RPCGoRPC
- Go 語言中的方法Go
- Go語言中的InterfaceGo
- 在Go語言中,怎樣使用Json的方法?GoJSON
- 在 Go 語言中增強 Cookie 的安全性GoCookie
- 【Go】四捨五入在go語言中為何如此困難Go
- Go 語言中的 collect 使用Go
- Go 語言中的外掛Go
- Go 語言中的 切片 --sliceGo
- Go語言中的TCP/IP網路程式設計GoTCP程式設計
- 聊聊 Go 語言中的物件導向程式設計Go物件程式設計
- 簡單分析Go語言中陣列的這些細節Go陣列
- GO 語言中的物件導向Go物件
- Go 語言中使用 ETCDGo
- 論go語言中goroutine的使用Go
- Go語言中的併發模式Go模式
- Go語言中的互動式CLI開發:survey庫簡介Go
- Go 語言中 strings 包常用方法Go
- Go語言中的單元測試Go
- 認識 Go 語言中的陣列Go陣列
- Go語言中的變數作用域Go變數
- 3.Go語言中常量,變數, 及其命名規則以及程式碼風格Go變數
- 在C語言中實現泛型程式設計C語言泛型程式設計
- 【Go學習筆記7】go語言中的模組(包)Go筆記
- Go 語言中的格式化輸出Go
- Go 語言中的兩種 slice 表示式Go
- go語言中import不允許迴圈包含GoImport
- 詳細解讀go語言中的chnanelGoNaN
- Go語言中mysql資料庫操作(一)GoMySql資料庫
- Go語言中時間輪的實現Go
- Go語言中defer的一些坑Go
- 聊聊Go語言中的陣列與切片Go陣列
- Go 語言中 defer 使用時有哪些陷阱?Go
- 9.Go語言中的流程控制Go