I see Go 反射reflect[rɪˈflekt] import “reflect“ 搞一个SQL生成器

本文的思路参考自:https://golangbot.com/reflection/ ,本文内容并非只是对原文的简单翻译,具体看下面的内容吧~!

 准备通过用反射搞一个通用的SQL构造器的例子,带大家理解掌握反射知识点。看了国外一个博主写的例子,觉得思路很好,我又对其进行了改进,让构造器的实现更丰富了些。

什么是反射?

答:反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能

为什么需要反射?

golang反射理解 - Go语言中文网 - Golang中文社区

当学习反射的时候,每个人首先会想到的问题都是 “为什么我们要在运行时检查变量的类型呢,程序里的变量在定义的时候我们不都已经给他们指定好类型了吗?” 确实是这样的,但也并非总是如此,看到这你可能心里会想,大哥,你在说什么呢,em... 还是先写一个简单的程序,解释一下。

package main

import (  
    "fmt"
)

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

在上面的程序里, 变量a的类型在编译时是已知的,我们在下一行打印了它的值和类型。

让我们理解一下 ”在运行时知道变量的类型的必要“。假设我们要编写一个简单的函数,它将一个结构体作为参数,并使用这个参数创建一个SQL插入语句。

package main

import (  
    "fmt"
)

type order struct {  
    ordId      int
    customerId int
}

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

我们需要写一个接收上面定义的结构体o作为参数,返回类似INSERT INTO order VALUES(20211103000001, 567)这样的SQL语句。这个函数定义写来很容易,比如像下面这样。

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:      20211103000001,
        customerId: 567,
    }
    fmt.Println(createQuery(o))
}

上面例子的createQuery使用参数o 的ordIdcustomerId字段创建SQL

现在让我们将我们的SQL创建函数定义地更抽象些,下面还是用程序附带说明举一个案例,比如我们想泛化我们的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 {  
}

现在我们的目标是,改造createQuery函数,让它能接受任何结构作为参数并基于结构字段创建INSERT 语句。比如如果传给createQuery的参数不再是order类型的结构体,而是employee类型的结构体时

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




### 那它应该返回的INSERT语句应该是

INSERT INTO employee (name, id, address, salary, country) 
VALUES("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore") 

由于createQuery 函数要适用于任何结构体,因此它需要一个 interface{}类型的参数。为了说明问题,简单起见,我们假定createQuery函数只处理包含string 和 int 类型字段的结构体。

编写这个createQuery函数的唯一方法是检查在运行时传递给它的参数的类型,找到它的字段,然后创建SQL。这里就是需要反射发挥用的地方啦。在后续步骤中,我们将学习如何使用Go语言的反射包来实现这一点。

Go语言的反射包      

   Go语言自带的reflect包实现了在运行时进行反射的功能,这个包可以帮助识别一个interface{}类型变量其底层的具体类型和值。我们的createQuery函数接收到一个interface{}类型的实参后,需要根据这个实参的底层类型和值去创建并返回INSERT语句,这正是反射包的作用所在。

在开始编写我们的通用SQL生成器函数之前,我们需要先了解一下reflect中我们会用到的几个类型和方法,接下来我们先逐个学习一下。

reflect.Type 和 reflect.Value

Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国

经过反射后interface{}类型的变量的底层具体类型由reflect.Type表示,底层值由reflect.Value表示。reflect包里有两个函数reflect.TypeOf() 和reflect.ValueOf() 分别能将interface{}类型的变量转换为reflect.Typereflect.Value。这两种类型是创建我们的SQL生成器函数的基础。

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


运行输出:

Type  main.order  
Value  {456 56} 

上面的程序里createQuery函数接收一个interface{}类型的实参,然后把实参传给了reflect.Typeofreflect.Valueof 函数的调用。从输出,我们可以看到程序输出了interface{}类型实参对应的底层具体类型和值。

Go语言反射的三法则

反射的三法则,他们是:

  1. 从接口值可以反射出反射对象。

  2. 从反射对象可反射出接口值。

  3. 要修改反射对象,其值必须可设置。

反射的第一条法则是,我们能够吧Go中的接口类型变量转换成反射对象,上面提到的reflect.TypeOf和 reflect.ValueOf 就是完成的这种转换。第二条指的是我们能把反射类型的变量再转换回到接口类型,最后一条则是与反射值是否可以被更改有关。

下面我们接着继续了解完成我们的SQL生成器需要的反射知识。

reflect.Kind

reflect包中还有一个非常重要的类型,reflect.Kind

reflect.Kindreflect.Type类型可能看起来很相似,从命名上也是,Kind和Type在英文的一些Phrase是可以互转使用的,不过在反射这块它们有挺大区别,从下面的程序中可以清楚地看到。

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 

通过输出让我们清楚了两者之间的区别。reflect.Type 表示接口的实际类型,即本例中main.order 而Kind表示类型的所属的种类,即main.order是一个「struct」类型,类似的类型map[string]string的Kind就该是「map」。

反射获取结构体字段的方法 

我们可以通过reflect.StructField类型的方法来获取结构体下字段的类型属性。reflect.StructField可以通过reflect.Type提供的下面两种方式拿到。

// 获取一个结构体内的字段数量
NumField() int
// 根据 index 获取结构体内字段的类型对象
Field(i int) StructField
// 根据字段名获取结构体内字段的类型对象
FieldByName(name string) (StructField, bool)

reflect.structField是一个struct类型,通过它我们又能在反射里知道字段的基本类型、Tag、是否已导出等属性。

type StructField struct {
 Name string
 Type      Type      // field type
 Tag       StructTag // field tag string
  ......
}

reflect.Type提供的获取Field信息的方法相对应,reflect.Value也提供了获取Field值的方法。

这块需要注意,不然容易迷惑。下面我们尝试一下通过反射拿到order结构体类型的字段名和值

package main

import (
 "fmt"
 "reflect"
)

type order struct {
 ordId      int
 customerId int
}

func createQuery(q interface{}) {
 t := reflect.TypeOf(q)
 if t.Kind() != reflect.Struct {
  panic("unsupported argument type!")
 }
 v := reflect.ValueOf(q)
 for i:=0; i < t.NumField(); i++ {
  fmt.Println("FieldName:", t.Field(i).Name, "FiledType:", t.Field(i).Type,
   "FiledValue:", v.Field(i))
 }

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

}


运行输出:

FieldName: ordId FiledType: int FiledValue: 456
FieldName: customerId FiledType: int FiledValue: 56

除了获取结构体字段名称和值之外,还能获取结构体字段的Tag

reflect.Value转换成实际值

现在离完成我们的SQL生成器还差最后一步,即还需要把reflect.Value转换成实际类型的值,reflect.Value实现了一系列Int()String()Float()这样的方法来完成其到实际类型值的转换。

反射搞一个SQL生成器

上面我们已经了解完写这个SQL生成器函数前所有的必备知识点啦,接下来就把他们串起来,加工完成createQuery函数。

SQL生成器完整的实现和测试代码如下:

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{}) string {
 t := reflect.TypeOf(q)
 v := reflect.ValueOf(q)
 if v.Kind() != reflect.Struct {
  panic("unsupported argument type!")
 }
 tableName := t.Name() // 通过结构体类型提取出SQL的表名
 sql := fmt.Sprintf("INSERT INTO %s ", tableName)
 columns := "("
 values := "VALUES ("
 for i := 0; i < v.NumField(); i++ {
  // 注意reflect.Value 也实现了NumField,Kind这些方法
  // 这里的v.Field(i).Kind()等价于t.Field(i).Type.Kind()
  switch v.Field(i).Kind() {
  case reflect.Int:
   if i == 0 {
    columns += fmt.Sprintf("%s", t.Field(i).Name)
    values += fmt.Sprintf("%d", v.Field(i).Int())
   } else {
    columns += fmt.Sprintf(", %s", t.Field(i).Name)
    values += fmt.Sprintf(", %d", v.Field(i).Int())
   }
  case reflect.String:
   if i == 0 {
    columns += fmt.Sprintf("%s", t.Field(i).Name)
    values += fmt.Sprintf("'%s'", v.Field(i).String())
   } else {
    columns += fmt.Sprintf(", %s", t.Field(i).Name)
    values += fmt.Sprintf(", '%s'", v.Field(i).String())
   }
  }
 }
 columns += "); "
 values += "); "
 sql += columns + values
 fmt.Println(sql)
 return sql
}

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

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

输出对应的标准SQL插入语句:

INSERT INTO order (ordId, customerId); VALUES (456, 56); 
INSERT INTO employee (name, id, address, salary, country); VALUES ('Naveen', 565, 'Coimbatore', 90000, 'India'); 

Clear is better than clever. Reflection is never clear.

Reflection is a very powerful and advanced concept in Go and it should be used with care. It is very difficult to write clear and maintainable code using reflection. It should be avoided wherever possible and should be used only when absolutely necessary.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘贤松

一本万利

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值