# 反射

#### 什么是反射

`反射`是指在运行时判断变量的类型和值的一种能力。

不过你也许会好奇，既然Golang是一种强类型的编程语言，所有数据的类型都是我们定义好的，为什么还要在运行时去检查变量的类型和值呢？

这种说法在大部分情况下是正确的，但不是绝对的。有些比较特殊的场景下确实需要反射这种强大的能力。典型的一个使用场景就是`ORM`，即对象关系映射。`ORM`是一种使用面向对象编程来操作数据库的模式，可以通过创建类或对象(在Golang中就是结构体类型和结构体变量)来操作数据库。

```go
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`语句。注意我们这里是想实现一个较为通用的函数，即可以接收任何类型的结构体变量作为入参，因此我们将参数类型声明为了`interface{}`。

比如，如果我们传下面的结构体变量作为入参：

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

那么我们期望的输出为：

```sql
insert into order values (1234, 567)
```

或者我们传入的结构体变量为：

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

那么我们期望的输出为：

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

这里我们为了简单起见，仅考虑属性为`int`或`string`类型。未了实现一个通用性的函数`createQuery`，我们只能在运行时去动态的判断参数的类型和值，这就需要借助反射。

#### `reflect`包

`reflect`包提供了我们需要的运行时的反射功能。我们可以借助它来判断`interface{}`变量内部具体的类型和值。这正是我们需要的功能。函数`createQuery`的入参就是`interface{}`类型，我们需要知道其内部具体类型和具体值来创建`SQL`语句。我么先来看下`reflect`都提供了哪些功能。

**reflect.Type reflect.Value**

`reflect`包中有两个方法`reflect.Typeof()`和`reflect.ValueOf()`用来获取某个变量的具体类型和具体值，这两个函数的返回值类型分别是`reflect.Type`和`reflect.Value`。下面通过一个例子来看看。

```go
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)
}
```

这里我们通过`reflect.TypeOf`和`reflect.ValueOf`获取到了`interface{}`类型的变量`q`的具体类型和具体值。输出结果为：

```
Type  main.order
Value  {456 56}
```

**reflect.Kind**

`reflect`还提供了另外一个判断大类的方法`reflect.Kind()`。这里的大类需要好好理解下，我们可以定义多种多样的结构体具体类型，但是Golang中的数据类型包括多个大类，比如结构体、数字、字符串等等。可以通过`reflect.Kind()`来判断是属于哪个大类。

```go
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)
    p := 1
    createQuery(p)
    s := "Hello World"
    createQuery(s)
}
```

该程序输出为：

```
Type  main.order
Kind  struct
Type  int
Kind  int
Type  string
Kind  string
```

注意这里的变量`o`反射的结果，`Type`对应的值为`main.order`，`Kind`对应的值为`struct`。

**NumField() Field()**

前面我们已经看到，`reflect.Value`类型的变量，是调用`reflect.ValueOf()`的返回值。如果`reflect.ValueOf()`的返回值类型是结构体类型，那么有两个方法，`NumField()`和`Field(i int)`，分别用来获取结构体变量的属性个数，以及第`i`个属性值。

```go
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)
}
```

因为`NumField()`仅能作用于结构体类型，因此我们首先判断了`q`是否是结构体类型。该程序输出为：

```
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`类型。

```go
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)
}
```

输出为：

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

#### 完整程序

熟悉完了`reflect`包相关API后我们看下完整程序。

```go
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)
}
```

这里我们首先判断函数的入参是否是结构体类型。然后通过`reflect.TypeOf(q).Name()`获取到了具体结构体类型名称。然后我们通过一个`switch case`语句来处理不同的属性类型。注意这里我们为了简单起见仅考虑的`int`和`string`类型。该程序输出为：

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

#### 不要滥用

Golang中的反射非常强大，但是也不要滥用，除非不得不用了再考虑使用，否则使用反射写的代码不太容易理解。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sunwenfei.gitbook.io/sunwenfei/golang/golang-ji-chu-jiao-cheng/fan-she.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
