2013-01-02 15 views
10

Pracuję nad systemem szablonów napisanym w Go, co oznacza, że ​​wymaga on liberalnego użycia pakietu reflect. W tej konkretnej sytuacji potrzebuję móc dynamicznie wywoływać metodę na interface{}. Dziwne jest to, że moja logika odbicia działa dobrze, o ile moje dane są znanego typu, ale nie, jeśli dane są typu interface{}.Dynamicznie wywołaj metodę na interfejsie {} niezależnie od typu odbiornika

W poniższym przykładzie widać, że logika w main() i Pass() jest identyczna. Jedyną różnicą jest to, czy dane jest znany typ lub znany typ wewnątrz interface{}

pobaw: http://play.golang.org/p/FTP3wgc0sZ

package main 

import (
    "fmt" 
    "reflect" 
) 

type Test struct { 
    Start string 
} 

func (t *Test) Finish() string { 
    return t.Start + "finish" 
} 

func Pass(i interface{}) { 
    _, ok := reflect.TypeOf(&i).MethodByName("Finish") 
    if ok { 
     fmt.Println(reflect.ValueOf(&i).MethodByName("Finish").Call([]reflect.Value{})[0]) 
    } else { 
     fmt.Println("Pass() fail") 
    } 
} 

func main() { 
    i := Test{Start: "start"} 

    Pass(i) 
    _, ok := reflect.TypeOf(&i).MethodByName("Finish") 
    if ok { 
     fmt.Println(reflect.ValueOf(&i).MethodByName("Finish").Call([]reflect.Value{})[0]) 
    } else { 
     fmt.Println("main() fail") 
    } 
} 

Po wykonaniu tego kodu otrzymujemy następujący wynik

Pass() fail 
startfinish 

Która oznacza, że ​​moja metodologia dynamicznego wywoływania metody działa poprawnie, z wyjątkiem scenariusza, w którym mój obiekt jest aktualnie w postaci interface{}.

Jeśli zamiast tego nie używam odbiornika wskaźnika i nie przepuszczam i, to działa zgodnie z oczekiwaniami.

pobaw: http://play.golang.org/p/myM0UXVYzX

To prowadzi mnie do przypuszczenia, że ​​moim problemem jest to, że nie można uzyskać dostępu do adresu I (&i), gdy jest to interface{}. Przeszukałem pakiet reflect i przetestowałem takie rzeczy, jak reflect.Value.Addr() i reflect.PtrTo(), ale nie mogłem pracować tak, jak potrzebowałem. Mam przeczucie, że ma coś wspólnego z faktem, że interface{} jest z definicji obiektem referencyjnym.

Odpowiedz

16

Dzięki @Jeremy muru wierzę, udało mi się rozwiązać mój problem. Podstawową kwestią jest wywoływanie metody o dynamicznej nazwie na interface{}. Istnieją 4 przypadki.

  1. interface{} bazowego danych jest wartość i odbiornik jest wartość
  2. interface{} bazowego danych jest wskaźnik i odbiornik jest wartość
  3. interface{} bazowego danych jest wartość i odbiornik jest wskaźnik
  4. interface{} bazowego danych jest wskaźnik i odbiornik wskaźnik

Używając odbicia możemy określić wartość podrzędną naszej int erface. Następnie, korzystając z dalszych rozważań, możemy wygenerować alternatywny typ danych dla naszego obecnego typu.Jeżeli dane przekazywane było wartością musimy wygenerować wskaźnik do niego

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 
} 

Teraz, gdy mamy oba typy danych możemy po prostu użyć każdego celu sprawdzenia istniejącego sposobu

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() 
} 

Dlatego z biorąc to pod uwagę, możemy skutecznie wywołać dowolną metodę, dynamicznie, niezależnie od tego, czy została zadeklarowana jako *receiver czy receiver.

Pełna Proof of Concept: 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")) 
} 
+0

Dzięki. Chociaż moje pytanie było inne, twoja odpowiedź pomogła mi ustalić, czy struktura ma na niej metodę z 'IsValid()'. – Matt

+0

Wpadłem na to (http://play.golang.org/p/v7lC0swB3s), próbując odpowiedzieć na moje własne pytanie (http://stackoverflow.com/questions/20167935/can-embedded-methods-access-parent pola) – Brenden

4

W twoim przykładzie nie można połączyć się z czymś, co obsługuje metodę Zakończ, ponieważ Wykończenie jest zdefiniowane tylko na wskaźnikach do testowych struktur. MethodByName robi dokładnie to, co powinno w tym przypadku. *Test != Test są to dwa różne typy całkowicie. Żadna ilość refleksji nie zmieni testu na * test. Naprawdę też nie powinno. Możesz użyć funkcji PtrTo, aby dowiedzieć się, czy dla typu wskaźnika została zdefiniowana metoda Zakończ, ale to nie pomoże Ci uzyskać wskaźnika do rzeczywistej wartości.

Wywołanie karnetów ze wskaźnikiem działa: http://play.golang.org/p/fICI3cqT4t

+1

Twoja odpowiedź była bardzo pomocna w znalezieniu odpowiedź na problem. Klucz brzmiał: "Żadna ilość refleksji nie zmieni testu na * test", co uświadomiło mi, że rozwiązanie problemu polega na znalezieniu sposobu, aby to dokładnie zrobić. Dodałem własną odpowiedź, która moim zdaniem udowodni, że można stworzyć "* test" z "testu" za pomocą refleksji. To pozwala mi wywoływać dowolną metodę z dowolnego 'interface {}' dynamicznie według nazwy, niezależnie od typu odbiornika. – Nucleon

+0

Rzeczywiście, możesz użyć interfejsu api, aby utworzyć nowy typ, który jest wskaźnikiem wartości i który będzie działał. Ale pod filozofią, która działa pod tobą, nie pracujesz już nad tym samym typem, który minąłeś. Tak czy inaczej cieszę się, że udało ci się rozwiązać problem. –

0

Jest to dobry przykład golang wdrożyć podobną funkcjonalność:

package main 

import "fmt" 

type Parent struct { 
    Attr1 string 
} 

type Parenter interface { 
    GetParent() Parent 
} 

type Child struct { 
    Parent //embed 
    Attr string 
} 

func (c Child) GetParent() Parent { 
    return c.Parent 
} 

func setf(p Parenter) { 
    fmt.Println(p) 
} 

func main() { 
    var ch Child 
    ch.Attr = "1" 
    ch.Attr1 = "2" 

    setf(ch) 
} 

kod z: https://groups.google.com/d/msg/golang-nuts/8lK0WsGqQ-0/HJgsYW_HCDoJ

Powiązane problemy