本教程有以下部分。
现在让我们一一讨论这些部分。
反射是程序在运行时检查其变量和值并查找其类型的能力。您可能不明白这意味着什么,但没关系。在本教程结束时,您将对反射有一个清晰的了解,所以请继续关注我。
在学习反射时,任何人遇到的第一个问题是,当程序中的每个变量都是由我们定义并且我们在编译时本身知道其类型时,为什么我们甚至需要在运行时检查变量并查找其类型。嗯,大多数时候都是如此,但并非总是如此。
让我解释一下我的意思。我们来写一个简单的程序。
package main
import (
"fmt"
)
func main() {
i := 10
fmt.Printf("%d %T", i, i)
}
在上面的程序中,i
的类型在编译时已知,我们在下一行中打印它。这里没什么神奇的。
现在让我们了解在运行时了解变量类型的必要性。假设我们想要编写一个简单的函数,它将接受一个结构体作为参数,并使用它创建一个 SQL 插入查询。
考虑以下程序,
package main
import (
"fmt"
)
type order struct {
ordId int
customerId int
}
func main() {
o := order{
ordId: 1234,
customerId: 567,
}
fmt.Println(o)
}
Run in playground
我们需要编写一个函数,将上面程序中的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))
}
Run in playground
第 12 行中的函数createQuery
。 使用 order
创建插入。该程序将输出
insert into order values(1234, 567)
现在让我们将查询创建器提升到一个新的水平。如果我们想概括我们的查询创建器并使其适用于任何结构,该怎么办?让我解释一下使用程序的含义。
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() {
}
我们的目标是完成上述程序第 16 行中的createQuery
函数,以便它将 any 作为参数并基于结构字段创建插入查询。
例如,如果我们传递下面的结构
1o := order {
2 ordId: 1234,
3 customerId: 567
4}
我们的createQuery
函数应该返回,
insert into order values (1234, 567)
同样如果我们通过
1 e := employee {
2 name: "Naveen",
3 id: 565,
4 address: "Science Park Road, Singapore",
5 salary: 90000,
6 country: "Singapore",
7 }
它应该返回,
insert into employee values("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")
由于该createQuery
函数应该适用于任何结构,因此它采用**interface{}**作为参数。为简单起见,我们将仅处理包含类型字段的结构string
,int
但这可以扩展到任何类型。
该createQuery
函数应该适用于任何结构。编写此函数的唯一方法是检查运行时传递给它的结构参数的类型,找到其字段,然后创建查询。这就是反射有用的地方。在本教程的后续步骤中,我们将学习如何使用该reflect
包来实现这一目标。
Reflect包实现了 Go 中的运行时反射。**Reflect 包有助于识别底层的具体类型和interface{}**变量的值。这正是我们所需要的。该createQuery
函数接受一个interface{}
参数,并且需要根据参数的具体类型和值创建查询interface{}
。这正是 Reflect 包可以帮助完成的事情。
在编写通用查询生成器程序之前,我们需要首先了解 Reflect 包中的一些类型和方法。让我们一一看看。
具体类型由reflect.Typeinterface{}
表示,底层值由reflect.Value表示 。有两个函数reflect.TypeOf()和reflect.ValueOf()分别返回和。这两种类型是创建查询生成器的基础。让我们写一个简单的例子来理解这两种类型。
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)
}
Run in playground
在上面的程序中,第 13 行中的 createQuery 函数将 interface{} 作为参数。在第 14 行中的 TypeOf 将 interface{} 作为参数并返回 reflect。包含传递的 interface{} 参数的具体类型的类型。同样第 15 行中的 ValueOf 函数将 interface{} 作为参数并返回 reflect。Value 包含传递的 interface{} 参数的基础值。
上面的程序打印出,
Type main.order
Value {456 56}
从输出中,我们可以看到程序打印了接口的具体类型和值。
反射包中有一种更重要的类型,称为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)
}
Run in playground
上面的程序输出,
Type main.order
Kind struct
我想您现在应该清楚两者之间的区别了。Type
表示interface{}的实际类型,在本例中为main.Order,并Kind
表示该类型的具体种类。在本例中,它是一个struct。
NumField() 方法返回结构中的字段数,Field(i int) 方法返回第 th 个字段的字段数。
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)
}
Run in playground
在上面的程序中,在第 14 行中,我们首先检查 of 是否为 a,因为该方法仅适用于结构。程序的其余部分是不言自明的。该程序输出
Number of fields 2
Field:0 type:reflect.Value value:456
Field:1 type:reflect.Value value:56
Int和String方法分别帮助将 提取reflect.Value
为int64
和string
。
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8func main() {
9 a := 56
10 x := reflect.ValueOf(a).Int()
11 fmt.Printf("type:%T value:%v\n", x, x)
12 b := "Naveen"
13 y := reflect.ValueOf(b).String()
14 fmt.Printf("type:%T value:%v\n", y, y)
15
16}
Run in playground
该程序打印
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)
}
Run in playground
在第 22 行中,我们首先检查传递的参数是否为 .在第 23 行中,我们使用该方法从其中获取结构的名称。在下一行中,我们使用并开始创建查询。
28行 检查当前字段是否为reflect.Int
,如果是的话,我们int64
使用该Int()
方法提取该字段的值。if else语句用于处理边缘情况。请添加日志以了解为什么需要它。类似的逻辑用于提取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)
在展示了反思的实际用途之后,现在出现了真正的问题。你应该使用反射吗?我想引用罗伯·派克关于使用反射的谚语来回答这个问题。
清晰胜于聪明。 反思从来都不是清晰的。
反射是 Go 中一个非常强大和先进的概念,应该谨慎使用。使用反射编写清晰且可维护的代码非常困难。应尽可能避免使用,并且仅在绝对必要时才使用。
本教程到此结束。希望你喜欢它。祝你有美好的一天。