Go反射指定执行方法的对象
2018-11-22
张子阳
分类: Go 语言
在上一篇 Go反射动态创建对象 中,我们在末尾又提出了三个问题,其中最后一个是执行方法的对象,我们希望能够自行指定方法是在哪个对象上执行的。这篇文章将演示如何实现这一过程。因为这已经是同一个主题的第3篇文章了,因此我们快速地过一遍代码。
修改Registry结构
现在,我们不再注册结构类型的方法,而是直接注册类型。
type Registry struct {
// key: Struct名称,例如:main.Computer
types map[string]reflect.Type
}
// 注册类型
func (x *Registry) RegisterType(t reflect.Type) error {
if x.types == nil {
x.types = make(map[string]reflect.Type)
}
// fmt.Println("t: \t", t.String())
// fmt.Println("t.Name(): \t", t.Name())
if _, ok := x.types[t.String()]; ok {
return errors.New("类型: " + t.String() + "已经注册过了")
}
x.types[strings.ToLower(t.String())] = t
return nil
}
我们要修改Call()方法的参数,将第一个参数的类型改为interface{}类型。当传入的是string时,则创建和类型名为此字符串相同的对象;当传入的是其他值时,则认为要在该对象上调用方法。
传入的应为指针类型,否则pv.MethodByName会找不到方法,并且下面的 field.Set() 也会失败,具体原因参看:The Laws of Reflection 的第3条。
// 在类型上调用方法
func (x *Registry) Call(target interface{}, methodName string, args interface{}) ([]interface{}, error) {
var pv reflect.Value
// 1. 验证target是string还是对象
switch target.(type) {
// 根据string创建一个新的对象,并在该对象上调用method方法
case string:
typeName := strings.ToLower(target.(string))
t, ok := x.types[typeName]
if !ok {
return nil, errors.New("类型 " + typeName + " 尚未注册")
}
pv = reflect.New(t)
default:
// 获取target的reflect.Value对象
pv = reflect.ValueOf(target)
}
if pv.Kind() != reflect.Ptr {
return nil, errors.New("target 应为指针,但实际为: " + pv.Type().String())
}
fmt.Println("pv.Kind:", pv.Kind())
fmt.Println("pv:\t", pv.String())
// 2. 获取调用对象上的method对象
method := pv.MethodByName(methodName)
if !method.IsValid() {
return nil, errors.New("方法 " + methodName + " 不存在.")
}
// 3. 检查传入的args信息
if args == nil {
args = []interface{}{}
}
argsType := reflect.TypeOf(args)
if argsType.Kind() != reflect.Slice {
return nil, errors.New("args 必须为 Slice, 而非 " + argsType.String())
}
// 4. 获取method的参数信息
methodType := method.Type()
numIn := methodType.NumIn()
// 4.1 判断method所需参数个数 和 实际传入参数个数是否匹配
arglist := reflect.ValueOf(args)
if numIn != arglist.Len() {
return nil, errors.New(fmt.Sprintf("%s 需要 %d 个参数,但传入了 %d 个参数", methodType, numIn, arglist.Len()))
}
// 4.2 判断method所需参数类型 和 实际传入参数的类型是否匹配
// mapType为:map[string]interface{} 的类型
mapType := reflect.TypeOf(make(map[string]interface{}))
// 保存方法调用的参数列表
argValues := []reflect.Value{}
for i := 0; i < numIn; i++ {
inType := methodType.In(i)
argValue := arglist.Index(i)
if argValue.Kind() != reflect.Interface {
return nil, errors.New(fmt.Sprintf("%s 的args参数应为 []interface{}", methodName))
}
argType := argValue.Elem().Type()
if argType != mapType && inType != argType {
return nil, errors.New(fmt.Sprintf("%s 的第%d个参数应为%s ,实际为%s ", methodName, i+1, inType, argType))
}
// 4. 构建方法的输入参数
// 如果argType是map[string]interface{}类型,则根据inType构建对象
// 否则,直接将interface下的实际值传加入argValues
if argType == mapType {
newArg := createStruct(inType, argValue.Elem().Interface().(map[string]interface{}))
argValues = append(argValues, newArg)
} else if argType == inType || (inType.Kind() == reflect.Interface && argType.Implements(inType)) {
argValues = append(argValues, argValue.Elem())
} else {
return nil, errors.New(fmt.Sprintf("%s 的第%d个参数应为%s,实际为%s ", methodName, i+1, inType, argType))
}
}
// 5. 调用方法,并返回结果
values := method.Call(argValues)
valueList := []interface{}{}
for i := 0; i < len(values); i++ {
valueList = append(valueList, values[i].Interface())
}
return valueList, nil
}
// 创建Struct对象
func createStruct(t reflect.Type, m map[string]interface{}) reflect.Value {
p := reflect.New(t)
if t.Kind() == reflect.Struct {
for k, v := range m {
field := p.Elem().FieldByName(k)
if field.IsValid() {
field.Set(reflect.ValueOf(v))
}
}
}
return p.Elem()
}
func main() {
testReflect2()
}
func testReflect2() {
reg := Registry{}
t := reflect.TypeOf(Computer{})
reg.RegisterType(t)
x := Computer{}
_, err := reg.Call(x, "Increase", nil)
if err != nil {
fmt.Println("Increase() error:", err.Error())
return
}
reg.Call(x, "Increase", nil)
reg.Call(x, "Increase", nil)
values, err := reg.Call(x, "GetCounter", nil)
if err != nil {
fmt.Println("GetCount() error:", err.Error())
} else if values != nil {
fmt.Println("GetCount() return: ", values[0])
}
}
测试1:每次都在新对象上调用方法
func testReflect1() {
reg := Registry{}
t := reflect.TypeOf(Computer{})
reg.RegisterType(t)
_, err := reg.Call("main.Computer", "Increase", nil)
if err != nil {
fmt.Println("Increase() error:", err.Error())
return
}
reg.Call("main.Computer", "Increase", nil)
reg.Call("main.Computer", "Increase", nil)
values, err := reg.Call("main.Computer", "GetCounter", nil)
if err != nil {
fmt.Println("GetCount() error:", err.Error())
} else if values != nil {
fmt.Println("GetCount() return: ", values[0])
}
}
这里GetCount()返回0,因为每次调用Increase()和GetCount()都在是一个新创建的对象上面。
这里对Registry中字典的key也做了优化,存入了package名称。因此调用时要加上main.(前面两篇文章没有加)
测试2:每次都在同一个对象上调用方法
func testReflect2() {
reg := Registry{}
t := reflect.TypeOf(Computer{})
reg.RegisterType(t)
x := &Computer{}
_, err := reg.Call(x, "Increase", nil)
if err != nil {
fmt.Println("Increase() error:", err.Error())
return
}
reg.Call(x, "Increase", nil)
reg.Call(x, "Increase", nil)
values, err := reg.Call(x, "GetCounter", nil)
if err != nil {
fmt.Println("GetCount() error:", err.Error())
} else if values != nil {
fmt.Println("GetCount() return: ", values[0])
}
}
在上面的代码中,因为方法的调用都在同一个x对象上面,所以最后GetCount()时返回了3。通过这样的修改,我们可以根据需要决定在调用方法时是创建新对象,还是在现有对象上调用。
至此,我们就完成了GO反射的常见操作。但仍有诸多的不足,比如说:没有处理variadic function,即参数的个数为可变的方法。例如:func Add(all ...int)。上面的方法会报错:需要X个参数,但是传入了Y个参数。此时需要根据 methodType.IsVariadic() 方法去进行判断和处理。
对于这些问题,基于已经掌握的知识,都可以进行解决,就不再示范了。通过这三篇文章的练习,已经可以应付大多数用到反射的场景。
感谢阅读,希望这篇文章能给你带来帮助!