Go语言中文网,致力于每日分享编码、开源等知识,欢迎关注我,会有意想不到的收获!
1. 图解反射
在使用反射之前,此文The Laws of Reflection必读。网上中文翻译版本不少,可以搜索阅读。
开始具体篇幅之前,先看一下反射三原则:
- Reflection goes from interface value to reflection object.
- Reflection goes from reflection object to interface value.
- To modify a reflection object, the value must be settable.
在三原则中,有两个关键词 interface value 与 reflection object。有点难理解,画张图可能你就懂了。
先看一下什么是反射对象 reflection object? 反射对象有很多,但是其中最关键的两个反射对象reflection object是:reflect.Type与reflect.Value.直白一点,就是对变量类型与值的抽象定义类,也可以说是变量的元信息的类定义.
再来,为什么是接口变量值 interface value, 不是变量值 variable value 或是对象值 object value 呢?因为后两者均不具备广泛性。在 Go 语言中,空接口 interface{}是可以作为一切类型值的通用类型使用。所以这里的接口值 interface value 可以理解为空接口变量值 interface{} value。
结合图示,将反射三原则归纳成一句话:
通过反射可以实现反射对象 reflection object与接口变量值 interface value之间的相互推导与转化, 如果通过反射修改对象变量的值,前提是对象变量本身是可修改的。
2. 反射的应用
在程序开发中是否需要使用反射功能,判断标准很简单,即是否需要用到变量的类型信息。这点不难判断,如何合理的使用反射才是难点。因为,反射不同于普通的功能函数,它对程序的性能是有损耗的,需要尽量避免在高频操作中使用反射。
举几个反射应用的场景例子:
2.1 判断未知对象是否实现具体接口
通常情况下,判断未知对象是否实现具体接口很简单,直接通过 变量名.(接口名) 类型验证的方式就可以判断。但是有例外,即框架代码实现中检查调用代码的情况。因为框架代码先实现,调用代码后实现,也就无法在框架代码中通过简单额类型验证的方式进行验证。
看看 grpc 的服务端注册接口就明白了。
grpcServer := grpc.NewServer()// 服务端实现注册pb.RegisterRouteGuideServer(grpcServer, &routeGuideServer{})
当注册的实现没有实现所有的服务接口时,程序就会报错。它是如何做的,可以直接查看pb.RegisterRouteGuideServer的实现代码。这里简单的写一段代码,原理相同:
//目标接口定义type Foo interface {Bar(int)} dst := (*Foo)(nil)dstType := reflect.TypeOf(dst).Elem()//验证未知变量 src 是否实现 Foo 目标接口srcType := reflect.TypeOf(src)if !srcType.Implements(dstType) {log.Fatalf("type %v that does not satisfy %v