『Go 內建庫第一季:reflect』

wuxiaoshen發表於2018-11-04

WOMEN_WHO_GO.png

大家好,我叫謝偉,是一名程式設計師。

近期會持續更新內建庫的學習內容,主要的參考文獻是 官方文件 和 原始碼。

本節的主題是:反射 -- 採用某種機制來實現對自己行為的描述和檢測,是型別的一種能力, 簡單的說是能夠獲取資料的型別和值。

所以反射的核心包括兩方面:型別(type)、值(value)

大綱:

  • 自己總結的反射的用法
  • 官方的反射的用法
  • 學到了什麼

自己總結的反射的用法

既然反射的用法包括兩方面,那麼日常編寫程式碼過程中,包括兩個方面:獲取型別、獲取值

下面演示最基本的用法,而且多用在結構體這種資料型別上。

  • reflect.TypeOf
  • reflect.ValueOf
var number int
number = 1
fmt.Println(reflect.TypeOf(number), reflect.ValueOf(number))

>> int 1
複製程式碼
type numberExample int
var numberOther numberExample
numberOther = 2
fmt.Println(reflect.TypeOf(numberOther), reflect.ValueOf(numberOther), reflect.ValueOf(numberOther).Kind())

>> main.numberExample 2 int
複製程式碼

可以看到,如何獲取資料型別,也可以看出 TypeOf 和 Kind 的區別,TypeOf 獲取的是基本資料型別和以 type 定義的型別;Kind 獲取的是底層的基本的資料型別,但不包括以 type 定義的型別。

但是最常見的關於反射的用法還是對結構體的處理。結構體在 Go 裡面是各種資料型別的資料的集合,同時還可以具有自己的方法。

結構體定義,還存在 tag, 在結構體和 json 相互序列化和反序列化之間的起作用。

定義如下結構體:

type Info struct {
	Name   string      `json:"name"`
	Age    interface{} `json:"age"`
	Prince float32     `json:"prince"`
}

type Groups struct {
	ID        uint     `json:"id"`
	Name      string   `json:"name"`
	Count     int      `json:"count"`
	CanFly    bool     `json:"can_fly"`
	GroupIDs  []int    `json:"group_ids"`
	GroupName []string `json:"group_name"`
	Info      `json:"info"`
}

複製程式碼

定義了兩個結構體,相應的欄位也宣告瞭 tag。

初始化:


// 匿名結構體,定義全域性變數

var globalValue struct {
	groups Groups
}

var valid struct {
	tag   string
	value Model
}

func init() {
	globalValue.groups = Groups{
		ID:        1,
		Name:      "xieWei",
		Count:     12,
		CanFly:    false,
		GroupIDs:  []int{100, 200, 300, 400},
		GroupName: []string{"what", "how", "when", "why"},
		Info: Info{
			Name:   "XieXiaoLu",
			Age:    20,
			Prince: 1.2345,
		},
	}

	valid.tag = "xieWei"

	valid.value = Model{
		ID:    1,
		Count: 2000,
		Name:  "Golang",
	}

}
複製程式碼

給結構體定義兩個方法,主要操作 型別和值。


func (g Groups) TypeHandler() {
}

func (g Groups) ValueHandler() {
}
複製程式碼

對結構體的反射操作,可以獲取結構體的屬性的型別、值和 tag。

自己思考下,獲取屬性的型別、值和 tag, 作者會設計些什麼內容?

獲取屬性、遍歷屬性、屬性的數目、按索引獲取屬性

func (g Groups) TypeHandler() {
}
複製程式碼
func (g Groups) TypeHandler() {
	typeGroups := reflect.TypeOf(g)
	name := typeGroups.Name()
	fmt.Println("Name: ", name, "Kind", typeGroups.Kind())
	for i := 0; i < typeGroups.NumField(); i++ {
		filed := typeGroups.Field(i)
		fmt.Println(filed.Name, "\t", filed.Tag, "\t", reflect.ValueOf(filed), "\t", filed.Type)
	}

	for i := 0; i < typeGroups.NumField(); i++ {
		filedByIndex := typeGroups.FieldByIndex([]int{i})
		filedByName, _ := typeGroups.FieldByName(filedByIndex.Name)
		fmt.Println(filedByIndex, filedByIndex.Name, filedByIndex.Type)
		fmt.Println(filedByName, filedByName.Name, filedByName.Type)
	}

	for i := 0; i < typeGroups.NumMethod(); i++ {
		method := typeGroups.Method(i)
		fmt.Println(method.Name, method.Type)
	}
}
複製程式碼

操作結構體的方法:


	for i := 0; i < typeGroups.NumMethod(); i++ {
		method := typeGroups.Method(i)
		fmt.Println(method.Name, method.Type)
	}
複製程式碼

操作值:

func (g Groups) ValueHandler() {
}
複製程式碼

func (g Groups) ValueHandler() {
	valueGroup := reflect.ValueOf(g)
	fmt.Println(valueGroup.NumField(), valueGroup.NumMethod(), valueGroup, reflect.ValueOf(&g).Elem())

	for i := 0; i < valueGroup.NumField(); i++ {
		field := valueGroup.Field(i)
		fmt.Println(field, field.Type(), field.Kind())
	}

	method := valueGroup.MethodByName("TypeHandler")
	fmt.Println(method, method.Kind(), method.Type())

	for i := 0; i < valueGroup.NumMethod(); i++ {
		method := valueGroup.Method(i)
		fmt.Println(method.Type())
	}
	ref := reflect.ValueOf(&g).Elem()

	fmt.Println(ref.FieldByName("Name"), ref.Field(0))
}
複製程式碼

為什麼是這樣的操作?

屬性、值、遍歷屬性、遍歷值

文件

為什麼這麼操作?

那當然看具體的 Type 的定義,是個介面。

type Type interface {
	Method(int) Method
        MethodByName(string) (Method, bool)
        NumMethod() int
        Name() string
        Kind() Kind
        Elem() Type
        Field(i int) StructField
        FieldByIndex(index []int) StructField
        FieldByName(name string) (StructField, bool)
        FieldByNameFunc(match func(string) bool) (StructField, bool)
        NumField() int
}

複製程式碼

可以看到,如何操作結構體屬性的型別。

具體的 Value 的定義,是個結構體。無屬性,有方法。

type Value struct {
    // contains filtered or unexported fields
}
func (v Value) Field(i int) Value
func (v Value) FieldByIndex(index []int) Value
func (v Value) FieldByName(name string) Value
func (v Value) FieldByNameFunc(match func(string) bool) Value
func (v Value) Method(i int) Value
func (v Value) MethodByName(name string) Value
func (v Value) NumField() int
func (v Value) NumMethod() int
複製程式碼

有時候,我們記不住 API,不知道哪些方法可以使用,怎麼辦?

以結構體為例?

  1. 回顧關於結構體的定義,結構體有什麼?
    1. 屬性
    2. 如何獲取屬性?按索引、按名稱
    3. 屬性的個數?
    4. 屬性的型別?名稱?
  2. 方法
    1. 方法的名稱
    2. 如何獲取方法
    3. 如何呼叫方法?
    4. 方法的個數

可以看出,嚴格上講,結構體的知識點就屬性(私有、公有), 方法(呼叫、宣告)。

所以看出,作者底層的結構體的定義也是關於這些的操作。

至此,我們始終沒有操作 結構體的 tag。

比如下面的結構體定義:


type Model struct {
	ID     uint   `xieWei:"number,max=10,min=1"`
	Name   string `xieWei:"string"`
	Count  int    `xieWei:"number,max=100,min=1"`
	CanFly bool   `xieWei:"bool,default=false"`
}
複製程式碼

我們經常看到在 gin 或者 gorm 內看到這些 tag的使用。

比如:gin 中

type PostParam string {
    ID uint `form:"id" binding:"required,omitempty"`
    Name string `form:"name" binding:"required"`
    Number int `form:"number" binding:"required,eq=1|eq=2"`
}

複製程式碼

上文根據 tag 規定欄位是否必須,空值是否省略,值的範圍

再比如:gorm 定義資料庫表


type Student struct {
    Name string `gorm:"type:"varchar,column:name" json:"name"`
    Number int `gorm:"type:"integer,column:number" json:"number"`
}


複製程式碼

上文根據 tag 規定欄位的型別,表列的名稱。

那是如何做到的呢?

答案:反射

通過反射,獲取到結構體的 tag, tag 是個字串,按照字串的操作,比如分割操作,獲取到型別等。

比如我們需要自己完成結構體的屬性的型別的檢驗。

func (m Model) Handler(name string) bool {

	typeModel := reflect.TypeOf(m)
	if tag, ok := typeModel.FieldByName(name); ok {
		if ok := strings.HasPrefix(string(tag.Tag), valid.tag); ok {
			//fmt.Println(validTagList[0])
			validTagList := strings.FieldsFunc(string(tag.Tag), func(r rune) bool {
				return r == ',' || r == '"'
			})
			switch validTagList[1] {
			case "number":
				{
					fmt.Println(validTagList[1:])
				}
			case "string":
				fmt.Println(validTagList[1:])

			case "bool":
				fmt.Println(validTagList[1:])

			}

		} else {
			return false
		}
	}
	return false
}

>>
[number max=10 min=1]
[string]
[number max=100 min=1]
[bool default=false]
[number min=1 max=1000]
複製程式碼

再進行後續的操作即可。

整體的思路是:

  • 獲取結構體屬性的 tag
  • 把 tag 按字串操作
  • 當然自己的校驗,最好規整好結構,比如 valid:number,max=10,min=1, 統一按這樣的操作,方便或許的解析。

總結:

反射是程式關於自身型別檢測的一種能力,通過內建庫的 reflect 可以獲取到變數、結構體的型別和值,還可以設定相應的值。

關於結構體的反射是使用 reflect 的一個比較核心的用處。

如何操作:

  • 結構體有屬性(公有、私有),有方法
  • 反射獲取屬性,可以通過遍歷、也可以通過索引值、還可以通過屬性名稱
  • 反射獲取方法,可以通過變數,也可以通過方法名稱

學到了什麼?

後記:學習,總想一口氣全部掌握知識,實際上不科學,第一次看,你可能只能掌握 10%, 正確的做法,應該是反覆看,尤其是你需要解決問題的時候。最後一定要融入自己的思考,僅僅只是塗塗畫畫寫寫,都能給你增加更多的記憶資訊。

相關文章