theme: healer-readable
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情
初心是记录和总结,自己学习Go语言的历程。如果能帮助到你,这是我的荣幸。
反射 - 万变不离其宗
反射能够将我们的程序一下子变得高大上!通过反射可以在程序运行期对程序本身进行访问和修改的能力。最最常见的一个例子就是接收用户传过来的值,将值赋给响应的变量。这种从值 -> 类型的感觉就是反射。举一个对比的例子,之前写程序都是:
var x int = 8
这种都是静态的,定死的变量。在程序运行过程中,永远不会被改变,难道你写程序都是写Demo这种重复且重复的事情吗!!当然不是(虽然学习过程中一直在做这种事情,哈哈)。
那么,B/S架构的程序,现在常用的数据传输是用户通过浏览器输入数据,前端使用JSON作为数据的传输格式,设想一下,我们接收到的不确定的值,如何对它进行封装呢?
结构体 + 反射的案例
俺们知道结构体这种类型的变量,作为多种类型的复合产品,最适合来形容物体多属性的特征。这里比如说还是:
小程在一家宠物店填写了 他和狗子: 小黑的信息,要求宠物店后端将收到的 Json进行封装,并输出。
【前提知识1】了解一下JSON和结构体互转
这种都是调用他人的函数库,就不详细介绍啦,了解用法就行。这里使用的是
encoding/json
,通过
json. 的方式,调用Marshal(转json)或Unmarshal(json转结构体)
结构体转JSON
buf, err := json.Marshal(定义好的结构体变量) //转换为json返回两个结果
if err != nil {
// 发现错误打印错误,并返回
fmt.Println("err = ", err)
return
}
// 输出结果
fmt.Println("json = ", string(buf))
JSON转结构体
// 定义结构体变量
var res 结构体
// 传入结构体变量指针作为第二个参数传入
if err := json.Unmarshal([]byte(jsonstr), &res); err != nil {
// 发现错误打印错误
fmt.Println(err)
}
// 输出结果
fmt.Println(res)
【前提知识2】结构体取类型+实例化的方法
反射用的是
reflect
内置函数库,我们先了解一下取类型和实例化
tp := reflect.TypeOf(x) // 获取到x的类型对象Type
instance := reflect.New(tp) //通过Type获得该类型的值类型反射接口interface
【正篇】定义结构体
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
type Dog struct {
Name string `json:"name"`
Color string `json:"color"`
}
讲解:结构体转义成json的时候,保持名称小写会让程序看的清爽,\`json:"name"\`,这种用法就是表示在json转义的时候名称按照name
这个值来。
【正篇】准备JSON语句模拟我们的数据
func main() {
personJson := `{"name":"程云来","age":23}`
dogJson := `{"name":"小黑","color":"黑棕色"}`
}
这个时候,我们最容易想到的是,直接定义两个类型变量,然后通过上面学的JSON和结构体互转的方式,直接转义:
直接定义对象通过方法转义
func main() {
personJson := `{"name":"程云来","age":23}`
dogJson := `{"name":"小黑","color":"黑棕色"}`
var person Person
var dog Dog
if err := json.Unmarshal([]byte(personJson), &person); err != nil {
fmt.Println(err)
}
fmt.Println(person)
if err := json.Unmarshal([]byte(dogJson), &dog); err != nil {
fmt.Println(err)
}
fmt.Println(dog)
}
等等,这重复代码太多了吧,而且代码显得不好看,能不能思考一下,通过这个对象调用什么方法的方式就能完成json.Unmarshal
【正篇】加上接口
如果,我们通过Person{}.toStruct
的方式,我们就能完成JSON转结构体的动作,这会美化我们的代码,而每个类的toStruct
方法应该抽取,利用反射的技术将JSON转结构体的方法,
type JsonToStruct interface {
toStruct()
}
// 2.实现接口,实现接口中应该将具体的封装操作通过反射的方式抽取出来,避免重复的代码
func (Person) toStruct(jsonStr string) Person {
// 调用反射的技术
}
func (Dog) toStruct(jsonStr string) Dog {
// 调用反射的技术
}
【正篇】利用反射完成JSON转结构体
观察JSON转结构体的方法语句:json.Unmarshal([]byte(jsonstr), &res)
,我们可以发现这里有两个变量:
- json语句变量:jsonstr
- 实际对象的指针值
所以方法也应该接收该两个变量,由于我们抽取出两者的共性,而对象类型是不唯一的,所以对象类型接收使用空接口,调用反射的技术实例化对象。
func newInstance(x interface{}, jsonStr string) interface{} {
tp := reflect.TypeOf(x)
instance := reflect.New(tp).Interface()
if err := json.Unmarshal([]byte(jsonStr), &instance); err != nil {
fmt.Println(err)
}
return instance
}
小坑注意!
instance := reflect.New(tp).Interface()
该语句为什么要这样写,通过文档得知New
方法返回的是Value
这个Value
是Go语言值的反射接口,它并不是一个类型的对象,所以不能直接将范围值使用,解决办法是在后面再调用Interface(),我们也通过文档看看
它相当于:
Var i interface{} = (v的底层值)
表示使用了一个空接口,接收了New方法实例化的实际对象
【正篇】完善实现接口
func (Person) toStruct(jsonStr string) Person {
instance := newInstance(Person{}, jsonStr)
person := instance.(*Person)
return *person
}
func (Dog) toStruct(jsonStr string) Dog {
instance := newInstance(Dog{}, jsonStr)
dog := instance.(*Dog)
return *dog
}
由于空接口是一种指针类型的数据,所以断言的时候需要加上*Person
完整代码附上
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
type Dog struct {
Name string `json:"name"`
Color string `json:"color"`
}
// 这里例子是模拟最底层的封装结构体,假设前端传入了一段json字符串,我们需要将它转为对应的struct
func main() {
personJson := `{"name":"程云来","age":23}`
dogJson := `{"name":"小黑","color":"黑棕色"}`
person := Person{}.toStruct(personJson)
dog := Dog{}.toStruct(dogJson)
fmt.Println(person)
fmt.Println(dog)
}
// 发现封装json的代码重复性太高了,进行改造,我们可以考虑封装一个结构体的方法,通过它接收一个json字符串后然后对其进行封装。
// 1.定义接口
type JsonToStruct interface {
toStruct()
}
// 2.实现接口,实现接口中应该将具体的封装操作通过反射的方式抽取出来,避免重复的代码
func (Person) toStruct(jsonStr string) Person {
instance := newInstance(Person{}, jsonStr)
person := instance.(*Person)
return *person
}
func (Dog) toStruct(jsonStr string) Dog {
instance := newInstance(Dog{}, jsonStr)
dog := instance.(*Dog)
return *dog
}
// 3.将封装的语句抽取封装
func newInstance(x interface{}, jsonStr string) interface{} {
tp := reflect.TypeOf(x)
instance := reflect.New(tp).Interface()
if err := json.Unmarshal([]byte(jsonStr), &instance); err != nil {
fmt.Println(err)
}
return instance
}
// 4.优化
type GetType interface {
toStruct()
}