34. 反射(Reflection)

欢迎阅读Golang系列教程第34篇:反射。

反射是Go中的高级用法之一。我将尽可能用简单清晰明了的方式来讲清楚它的用法。

什么是反射?

反射是程序在运行时检查一个变量的值和类型的这样一种功能。您可能不明白这意味着什么,但是没关系。在本教程结束时,您将对反射有了一个清晰的了解。

为什么需要检查变量并找到其类型?

任何人在学习反射时都会遇到的第一个问题就是:程序中的每个变量,在我们定义和编译时就已经知道了它的类型,为什么需要在运行时还要去检查变量并找到其类型呢?嗯,在大多数情况下都是如此,但并非总是如此。

让我解释一下我的意思。让我们编写一个简单的程序。

package main

import (  
    "fmt"
)

func main() {  
    i := 10
    fmt.Printf("%d %T", i, i)
}

在线运行

在上面的程序中,变量i的类型在编译时是已知的,我们在下一行中打印它。这个很简单。

输出:

10 int

现在让我们了解下在运行时需要知道变量和类型的需求。假设我们要编写一个简单的函数,该函数将struct作为参数并使用它创建一个SQL插入查询语句。

考虑以下程序,

package main

import (  
    "fmt"
)

type order struct {  
    ordId      int
    customerId int
}

func main() {  
    o := order{
        ordId:      1234,
        customerId: 567,
    }
    fmt.Println(o)
}

在线运行

我们需要编写一个函数,该函数将结构体o作为参数,并返回以下SQL插入语句:

insert into order values(1234, 567)  

此函数写起来还是比较简单的,现在就开始吧。

package main

import (  
    "fmt"
)

type order struct {  
    ordId      int
    customerId int
}

func createQuery(o order) string {  
    i := fmt.Sprintf("insert into order values(%d, %d)", o.ordId, o.customerId)
    return i
}

func main() {  
    o := order{
        ordId:      1234,
        customerId: 567,
    }
    fmt.Println(createQuery(o))
}

在线运行

函数createQuery通过使用结构体o中的ordIdcustomerId字段创建了SQL插入语句。该程序将输出:

insert into order values(1234, 567)  

现在,让我们把查询生成器这个函数的难度加大。如果我们要泛化这个SQL成成函数并把它适用于任何struct,那么该怎么办呢。让我用代码来说明一下:

package main

type order struct {  
    ordId      int
    customerId int
}

type employee struct {  
    name string
    id int
    address string
    salary int
    country string
}

func createQuery(q interface{}) string {  
}

func main() {

}

我们的目标是完成createQuery函数的功能,使得它就可以将任何struct作为参数并基于这个结构体里面的字段,创建一个SQL插入语句。

比如,如果我们传递下面的结构体,

o := order {  
    ordId: 1234,
    customerId: 567
}

我们的createQuery函数应该返回:

insert into order values (1234, 567)  

同样,如果我们这样传:

 e := employee {
        name: "Naveen",
        id: 565,
        address: "Science Park Road, Singapore",
        salary: 90000,
        country: "Singapore",
    }

它应该返回:

insert into employee values("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")  

由于createQuery函数的参数可以传任何结构体,因此这个结构体参数的类型得申明为interface {}类型。为简单起见,我们只会处理包含类型字段为stringint的结构体参数,当然也可以扩展到任何类型。

由于createQuery函数的参数可以传任何结构体。编写此函数的唯一方法是在运行时检查传递给它的struct参数的类型,找到其字段和值,然后创建SQL查询语句。这是反射有用的地方。在本教程的后续步骤中,我们将学习如何使用reflect包来实现这一目标。

reflect package

go语言中reflect 包就可以实现运行时反射。reflect 可以识别底层的具体类型和interface {}变量的值,这正是我们所需要的。该createQuery函数接受一个interface{}参数,并且需要根据interface{}参数的具体类型和值来创建查询。这正是reflect包可以帮助实现这个功能。

在编写通用查询生成器程序之前,我们需要首先了解reflect包中的几种类型和方法。让我们一一看下。

reflect.Type 和 reflect.Value

interface{}的具体类型由reflect.Type表示,值由reflect.Value表示 。有两个函数reflect.TypeOf()reflect.ValueOf()分别返回reflect.Typereflect.Value。这两种类型是创建查询生成器的基础。让我们写一个简单的例子来理解这两种类型。

package main

import (  
    "fmt"
    "reflect"
)

type order struct {  
    ordId      int
    customerId int
}

func createQuery(q interface{}) {  
    t := reflect.TypeOf(q)
    v := reflect.ValueOf(q)
    fmt.Println("Type ", t)
    fmt.Println("Value ", v)


}
func main() {  
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

}

在线运行

在上面的程序中,第13行的createQuery函数使用了一个interface {}作为参数。第14行中的函数reflect.TypeOf()使用interface {}作为参数,并返回reflect.Type,其中包含传递的interface {}参数的具体类型。类似地,第15行中的reflect.ValueOf函数使用interface {}作为参数并返回reflect.Value,包含所传递的interface {}参数的值。

以上程序打印,

Type  main.order  
Value  {456 56}  

从输出中,我们可以看到程序打印了具体的类型和接口的值。

reflect.Kind

反射包中还有一种更重要的类型称为Kind。

反射包中的类型Kind和类型Type可能看起来相似,但是它们之间的区别可以从下面的程序中清楚看出。

package main

import (  
    "fmt"
    "reflect"
)

type order struct {  
    ordId      int
    customerId int
}

func createQuery(q interface{}) {  
    t := reflect.TypeOf(q)
    k := t.Kind()
    fmt.Println("Type ", t)
    fmt.Println("Kind ", k)


}
func main() {  
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

}

在线运行

上面的程序输出,

Type  main.order  
Kind  struct  

我想您现在会清楚两者之间的区别。Type表示interface{}的实际类型,在本例中为main.Order,而Kind表示类型的特定类型。在这种情况下,它是一个struct

NumField() 和 Field() 方法

NumField()方法,返回一个结构体中字段的数量。Field(i int)方法,返回第i个字段的reflect.Value

package main

import (  
    "fmt"
    "reflect"
)

type order struct {  
    ordId      int
    customerId int
}

func createQuery(q interface{}) {  
    if reflect.ValueOf(q).Kind() == reflect.Struct {
        v := reflect.ValueOf(q)
        fmt.Println("Number of fields", v.NumField())
        for i := 0; i < v.NumField(); i++ {
            fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i))
        }
    }

}
func main() {  
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)
}

在线运行

在上面的程序中,第14行,我们首先检查qKind() 是否为struct,因为该NumField`方法仅适用于struct。该程序输出:

Number of fields 2  
Field:0 type:reflect.Value value:456  
Field:1 type:reflect.Value value:56  

Int() 和 String()方法

IntString方法分别帮助提取reflect.Valueaint64string

package main

import (  
    "fmt"
    "reflect"
)

func main() {  
    a := 56
    x := reflect.ValueOf(a).Int()
    fmt.Printf("type:%T value:%v\n", x, x)
    b := "Naveen"
    y := reflect.ValueOf(b).String()
    fmt.Printf("type:%T value:%v\n", y, y)

}

在线运行

在上面的程序中,在第10行和13行,我们提取reflect.Value为int64string。该程序打印:

type:int64 value:56  
type:string value:Naveen  

完成程序

现在我们有足够的知识来完成我们的查询生成器,让我们继续进行。

package main

import (  
    "fmt"
    "reflect"
)

type order struct {  
    ordId      int
    customerId int
}

type employee struct {  
    name    string
    id      int
    address string
    salary  int
    country string
}

func createQuery(q interface{}) {  
    if reflect.ValueOf(q).Kind() == reflect.Struct {
        t := reflect.TypeOf(q).Name()
        query := fmt.Sprintf("insert into %s values(", t)
        v := reflect.ValueOf(q)
        for i := 0; i < v.NumField(); i++ {
            switch v.Field(i).Kind() {
            case reflect.Int:
                if i == 0 {
                    query = fmt.Sprintf("%s%d", query, v.Field(i).Int())
                } else {
                    query = fmt.Sprintf("%s, %d", query, v.Field(i).Int())
                }
            case reflect.String:
                if i == 0 {
                    query = fmt.Sprintf("%s\"%s\"", query, v.Field(i).String())
                } else {
                    query = fmt.Sprintf("%s, \"%s\"", query, v.Field(i).String())
                }
            default:
                fmt.Println("Unsupported type")
                return
            }
        }
        query = fmt.Sprintf("%s)", query)
        fmt.Println(query)
        return

    }
    fmt.Println("unsupported type")
}

func main() {  
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

    e := employee{
        name:    "Naveen",
        id:      565,
        address: "Coimbatore",
        salary:  90000,
        country: "India",
    }
    createQuery(e)
    i := 90
    createQuery(i)

}

在线运行

在第 22行,首先检查传递的参数是否为struct。在第 23行我们从reflect.Type使用该Name()方法的结构获得名称。在下一行中,我们使用t并开始创建查询。

该案例声明符合要求。第28行检查当前字段是否为reflect.Int,如果是这种情况,我们int64使用Int()方法提取该字段的值。如果其他语句用来处理边缘情形。请添加日志以了解为什么需要它。

我们还添加了检查功能,以防止将不支持的类型传递给 createQuery函数时程序崩溃。该程序的其余部分不言自明。

该程序打印,

insert into order values(456, 56)  
insert into employee values("Naveen", 565, "Coimbatore", 90000, "India")  
unsupported type  

如何将字段名称添加到输出查询中呢?请尝试更改程序以打印格式的查询,我将其留给读者作为练习。

insert into order(ordId, customerId) values(456, 56)  

答案:

package main

import (
	"fmt"
	"reflect"
)

type order struct {
	ordId      int
	customerId int
}

type employee struct {
	name    string
	id      int
	address string
	salary  int
	country string
}

func createQuery(q interface{}) {
	if reflect.ValueOf(q).Kind() == reflect.Struct {
		t := reflect.TypeOf(q).Name()
		query := fmt.Sprintf("insert into %s(", t)
		v := reflect.ValueOf(q)

		//
		p := reflect.TypeOf(q)

		for i := 0; i < p.NumField(); i++ {

			if i == 0 {
				query = fmt.Sprintf("%s`%s`", query, p.Field(i).Name)
			} else {
				query = fmt.Sprintf("%s, `%s`", query, p.Field(i).Name)
			}

		}
		query = fmt.Sprintf("%s) values(", query)

		for i := 0; i < v.NumField(); i++ {
			switch v.Field(i).Kind() {
			case reflect.Int:
				if i == 0 {
					query = fmt.Sprintf("%s%d", query, v.Field(i).Int())
				} else {
					query = fmt.Sprintf("%s, %d", query, v.Field(i).Int())
				}
			case reflect.String:
				if i == 0 {
					query = fmt.Sprintf("%s\"%s\"", query, v.Field(i).String())
				} else {
					query = fmt.Sprintf("%s, \"%s\"", query, v.Field(i).String())
				}
			default:
				fmt.Println("Unsupported type")
				return
			}
		}
		query = fmt.Sprintf("%s)", query)
		fmt.Println(query)
		return

	}
	fmt.Println("unsupported type")
}

func main() {
	o := order{
		ordId:      456,
		customerId: 56,
	}
	createQuery(o)

	e := employee{
		name:    "Naveen",
		id:      565,
		address: "Coimbatore",
		salary:  90000,
		country: "India",
	}
	createQuery(e)
	i := 90
	createQuery(i)

}

应该使用反射吗?

上面我们通过一些例子展示了反射的实际用途,那么问题来了?。你应该使用反射吗?我想引用罗伯·派克(Rob Pike)关于使用反射的谚语,它回答了这个问题。

Clear is better than clever. Reflection is never clear.
清晰胜于聪明。反思从未明确。

反射是Go中非常强大且先进的概念,应谨慎使用。使用反射编写清晰、可维护的代码非常困难。除非是非用不可的时候,一般情况下应尽可能避免使用它。

本次教程结尾了,希望你喜能欢。祝你有美好的一天。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值