『Go 内置库第一季:reflect』

WOMEN_WHO_GO.png

你们好,我叫谢伟,是一名程序员。程序员

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

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

因此反射的核心包括两方面:类型(type)、值(value)bash

大纲:学习

  • 本身总结的反射的用法
  • 官方的反射的用法
  • 学到了什么

本身总结的反射的用法

既然反射的用法包括两方面,那么平常编写代码过程当中,包括两个方面:获取类型、获取值ui

下面演示最基本的用法,并且多用在结构体这种数据类型上。spa

  • 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 里面是各类数据类型的数据的集合,同时还能够具备本身的方法。code

结构体定义,还存在 tag, 在结构体和 json 相互序列化和反序列化之间的起做用。orm

定义以下结构体:

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%, 正确的作法,应该是反复看,尤为是你须要解决问题的时候。最后必定要融入本身的思考,仅仅只是涂涂画画写写,都能给你增长更多的记忆信息。

相关文章
相关标签/搜索