Ответ 1
Благодаря @Jeremy Wall я считаю, что смог решить мою проблему. Основная проблема - вызов динамически названного метода на interface{}
. Есть 4 случая.
-
interface{}
базовые данные - это значение, а получатель - значение -
interface{}
базовыми данными являются указатель и получатель - значение -
interface{}
базовые данные - это значение, а получатель - указатель -
interface{}
базовые данные - указатель, а приемник - указатель
Используя отражение, мы можем определить значение подрыва нашего интерфейса. Затем, используя дополнительное отражение, мы можем сгенерировать альтернативный тип данных для нашего текущего типа. Если переданные данные были значением, нам нужно сгенерировать указатель на него
value := reflect.ValueOf(data)
if value.Type().Kind() == reflect.Ptr {
ptr = value
value = ptr.Elem() // acquire value referenced by pointer
} else {
ptr = reflect.New(reflect.TypeOf(i)) // create new pointer
temp := ptr.Elem() // create variable to value of pointer
temp.Set(value) // set value of variable to our passed in value
}
Теперь, когда у нас есть оба типа данных, мы можем просто использовать их для проверки существующего метода
var finalMethod reflect.Value
method := value.MethodByName(methodName)
if method.IsValid() {
finalMethod = method
}
// check for method on pointer
method = ptr.MethodByName(methodName)
if method.IsValid() {
finalMethod = method
}
if (finalMethod.IsValid()) {
return finalMethod.Call([]reflect.Value{})[0].String()
}
Поэтому, имея в виду это, мы можем эффективно вызвать любой метод, динамически, объявленный как *receiver
или receiver
.
Полное доказательство концепции: http://play.golang.org/p/AU-Km5VjZs
package main
import (
"fmt"
"reflect"
)
type Test struct {
Start string
}
// value receiver
func (t Test) Finish() string {
return t.Start + "finish"
}
// pointer receiver
func (t *Test) Another() string {
return t.Start + "another"
}
func CallMethod(i interface{}, methodName string) interface{} {
var ptr reflect.Value
var value reflect.Value
var finalMethod reflect.Value
value = reflect.ValueOf(i)
// if we start with a pointer, we need to get value pointed to
// if we start with a value, we need to get a pointer to that value
if value.Type().Kind() == reflect.Ptr {
ptr = value
value = ptr.Elem()
} else {
ptr = reflect.New(reflect.TypeOf(i))
temp := ptr.Elem()
temp.Set(value)
}
// check for method on value
method := value.MethodByName(methodName)
if method.IsValid() {
finalMethod = method
}
// check for method on pointer
method = ptr.MethodByName(methodName)
if method.IsValid() {
finalMethod = method
}
if (finalMethod.IsValid()) {
return finalMethod.Call([]reflect.Value{})[0].Interface()
}
// return or panic, method not found of either type
return ""
}
func main() {
i := Test{Start: "start"}
j := Test{Start: "start2"}
fmt.Println(CallMethod(i, "Finish"))
fmt.Println(CallMethod(&i, "Finish"))
fmt.Println(CallMethod(i, "Another"))
fmt.Println(CallMethod(&i, "Another"))
fmt.Println(CallMethod(j, "Finish"))
fmt.Println(CallMethod(&j, "Finish"))
fmt.Println(CallMethod(j, "Another"))
fmt.Println(CallMethod(&j, "Another"))
}