您好,登錄后才能下訂單哦!
這篇文章主要為大家分析了如何在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
看到這張圖我們大概就明白應該怎樣做了,目標是編寫一個綁定方法,必須建立一個綁定關系,把這個結構體加上一個 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語言中利用反射精簡代碼”就介紹到這了,更多相關內容可以搜索億速云以前的文章,希望能夠幫助大家答疑解惑,請多多支持億速云網站!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。