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() 方法去进行判断和处理。
对于这些问题,基于已经掌握的知识,都可以进行解决,就不再示范了。通过这三篇文章的练习,已经可以应付大多数用到反射的场景。
感谢阅读,希望这篇文章能给你带来帮助!