『Gopher 』
首发于『Gopher 』
『Go 内置库第一季:reflect』

『Go 内置库第一季:reflect』

大家好,我叫谢伟,是一名程序员。

近期会持续更新内置库的学习内容,主要的参考文献是 官方文档 和 源代码。

本节的主题是:反射 -- 采用某种机制来实现对自己行为的描述和检测,是类型的一种能力, 简单的说是能够获取数据的类型和值。

所以反射的核心包括两方面:类型(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. 属性的类型?名称?


  1. 方法
    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%, 正确的做法,应该是反复看,尤其是你需要解决问题的时候。最后一定要融入自己的思考,仅仅只是涂涂画画写写,都能给你增加更多的记忆信息。
发布于 2018-11-04

文章被以下专栏收录