文章目录
导言
- 原文链接: Part 34: Reflection
- If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.
反射
反射是 GO
语言 的高级主题之一。我会试着用简单的语言,将它说清楚。
反射是什么?
反射是一种能力,在程序运行时,这种能力可以检测程序变量,得出变量的类型、值。
你可能还没理解这句话的意思,无所谓。在这篇文章的最后,你将对反射有清晰的认识。所以,跟着我的思路吧~
为什么需要得到变量的类型?
在学习反射的过程中,所有人都会提出的一个问题是:为什么在程序时,我们需要去检测程序的变量,并得出它们的类型?我们定义了所有变量,在编译时,我们不是已经清楚这些变量的类型了么?
嗯,的确如此,但不总是如此。
为了解释我的意思,我们来写个简单的程序。
package main
import (
"fmt"
)
func main() {
i := 10
fmt.Printf("%d %T", i, i)
}
在上面的程序中,编译时,我们知道了 i
的类型。在下一行,我们将它打印出来。一切非常自然~
10 int
现在,我们来了解一下:在运行时,为什么需要知道变量的类型。
假设我们要写一个简单的函数,这个函数将结构体作为参数,并在该结构体的基础上,创建一条 SQL
插入语句。
看看下面的程序:
package main
import (
"fmt"
)
type order struct {
ordId int
customerId int
}
func main() {
o := order{
ordId: 1234,
customerId: 567,
}
}
我们需要写一个函数,它将 结构体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))
}
第 12
行的 createQuery
函数,通过 o
结构体 的 ordId
、customerId
字段, 创建了一条插入语句。
程序输出如下:
insert into order values(1234, 567)
现在,升级一下。假如对于任何结构体,我们都希望能生成相应的 SQL
插入语句,我们应该怎么做呢?
让我写一个程序,来解释我的意思:
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
函数,该函数都能创建出相应的 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{}
。
为了简单起见,我们只处理 只有string
类型、int
类型 字段的结构体。
这里只是简单起见,实际上可以拓展到任何类型。
由于 createQuery
函数 需要适应任意结构体,所以在写这个函数时,我们必须得到 该结构体 的类型,之后得到它的字段,最后创建 SQL
语句。反射就是用来做这事的。
下面,我们将学习怎么利用 reflect
包,实现我们的目的。
reflect
包
在 Go
语言 中,reflect
包 实现了运行时反射。reflect
包 能够获取 interface{}
变量 底层的具体类型和值。这正是我们需要的。
createQuery
函数 将 interface{}
作为参数,而 SQL
语句 的创建依赖于该 interface{}
的具体类型和值。reflect
包 能实现这个目的。
在写我们的通用语句生成器前,我们必须先知道 reflect
包 中的一些类型和方法。
reflect.Type
和 reflect.Value
reflect.Type
能存储 interface{}
的具体类型,而 reflect.Value
能存储 interface{}
的底层数值。reflect.TypeOf()
、reflect.ValueOf()
能相应地返回 reflect.Type
、reflect.Value
。reflect
包 的这 2
个类型,是创建语句生成器的基础。
接下来,我来写一个程序,带大家理解上面的 2
个类型。
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{}
作为参数,返回 interface{}
的 reflect.Type
— 这个 reflect.Type
包含了 interface{}
的具体类型。同样地,第 15
行的 reflect.ValueOf
函数 将 interface{}
作为参数, 返回 interface{}
的 reflect.Value
— 这个 reflect.Value
包含了 interface{}
的底层数值。
程序输出如下:
Type main.order
Value {456 56}
从输出中可以看出,程序得到了 interface{}
的具体类型和值。
reflect.Kind
reflect
包 还有一个重要的类型 — reflect.Kind
。
reflect
包 的 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{}
的实际类型,而 Kind
表示该 interface{}
类型的特定类别。
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
行,我们先检查 q
的 Kind
是否为 struct
— 因为只有结构体才能使用 NumField
方法。其它的代码不言自明。
程序输出如下:
Number of fields 2
Field:0 type:reflect.Value value:456
Field:1 type:reflect.Value value:56
Int
和 String
方法
Int
、String
方法 会分别把 reflect.Value
转换为 int64
、string
类型。
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
行,我们将 reflect.Value
转换为 int64
,而在第 13
行,我们将 reflect.Value
转换为 string
。
程序输出如下:
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.Value
的 Name
方法,我们获得了结构体的名字。在下一行,我们使用 t
进行语句创建。
第 28
行的 case
,能判断当前字段是否为 reflect.Int
,如果是,我们就用 Int
方法,将该字段的值转换为 int64
类型。下面的 if else
语句 用于处理边界情况,通过输出日志,你可以理解它的用处。在第 34
行,我们使用相同的逻辑处理了 string
。
我们还为上面的程序添加了一些校验逻辑,从而防止程序因类型问题而奔溃。程序剩余的代码不言自明。
我推荐你在适当的地方添加日志,并检查输出,以更好地理解这个程序。
程序输出如下:
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)
// 之前的格式是这样的
insert into order values (1234, 567)
反射应被使用吗?
在展示了反射的实际用途后,这出现了一个问题:我们应该使用反射吗?
这里,我引用 Rob Pick
的一句话。
Clear is better than clever. Reflection is never clear.
个人翻译:清晰胜于巧妙,反射一点也不清晰。
在 Go
中,反射是一个功能强大且先进的概念,我们应该谨慎使用它。使用反射,我们很难写出清晰且易于维护的代码。因此,我们应尽可能避免使用反射,只在绝对必要时使用它。
这就是反射了~
祝你开心~
原作者留言
优质内容来之不易,您可以通过该 链接 为我捐赠。
最后
感谢原作者的优质内容。
欢迎指出文中的任何错误。