2015-05-01 4 views
5

Ситуация у меня сейчас такая же, как был задан вопрос о том, в этой теме: Meaning of a struct with embedded anonymous interface?Перейти к отражению с интерфейсом, встроенным в структуру - как определить «реальные» функции?

type A interface { 
    Foo() string 
    } 

    type B struct { 
    A 
    bar string 
    } 

идиоматически, исходя из фоном в ООП языках, что это выглядит, как этот шаблон «пытается сказать» мне заключается в том, что B должен реализовать интерфейс A. Но я до сих пор понимаю, что «Go отличается». Таким образом, вместо проверки времени компиляции я ожидал в первый, это счастливый скомпилировать с или без

func (B) Foo() string { .... } 

настоящее время. Как упомянуто выше, вопрос (перефразируемый): «использование встроенных интерфейсов в структурах отлично подходит, когда вы только хотите реализовать/часть/интерфейса».

Предположительно, это связано с тем, что то, что происходит с этим встраиванием, точно так же, как и в каждом другом случае - значение типа B будет иметь значение анонимного интерфейса типа A в качестве поля. Лично, когда я нахожу, что ортогональность утешает, я также сбиваю с толку, что пакет отражения затем позволит мне получить методы A непосредственно из типа B таким образом, а не error/nil, если не существует метода с приемником B. Но - это вопрос не о мышлении за что - это о том, что значение интерфейса инициализируется после b := B{}:

func main() { 
    bType := reflect.TypeOf(B{}) 
    bMeth, has := bType.MethodByName("Foo") 
    if has { 
     fmt.Printf("HAS IT: %s\n",bMeth.Type.Kind()) 
     res := bMeth.Func.Call([]reflect.Value{reflect.ValueOf(B{})}) 
     val := res[0].Interface() 
     fmt.Println(val) 
    } else { 
     fmt.Println("DOESNT HAS IT") 
    } 
} 

Когда запускается, он вызывает ужасную панику

HAS IT: func 
panic: runtime error: invalid memory address or nil pointer dereference 

. .. или не - в зависимости от того, смог ли компилятор/время выполнения найти вышеуказанный метод. Итак: Как я могу обнаружить эту ситуацию до ее запуска?

То есть - есть ли что-то о значении bMeth, которое я могу использовать, чтобы увидеть, что нет реальной реализации в возвращаемых возвратом значениях метода и func? Точнее ли это что-то вроде «является указателем на функцию в таблице функций анонимного значения интерфейса в ноль» или что именно происходит с методами, которые вы извлекаете из интерфейса с отражением, где нет реализации?

Обертывание всей вещи в goroutine и попытка запустить функцию при отсрочке/панике - это не ответ - не только из-за расхода паники/отсрочки, но и потому, что функция вообще может, если она делает есть, есть побочные эффекты, которые я не хочу прямо сейчас ...

Я хочу что-то вроде реализации во время выполнения, которая отражает проверку типа компилятора? Или есть более простой способ? Я думаю об этом неправильно?

Above example in a Go playground

+0

Тип 'B' имеет метод' Foo' - нет такой вещи, как «реальный» или «не настоящий» метод или функция.Вы можете либо взять эту информацию как есть, либо вы можете вызвать метод и посмотреть, что произойдет (для восстановления не требуется goroutine, просто отложенная функция). – JimB

+0

Как я уже сказал, отложить/восстановить не вариант, потому что функция может существовать и иметь побочные эффекты. – BadZen

+1

Тогда, если вы не собираетесь называть это, это имеет значение? Какую проблему вы пытаетесь решить, если знаете, что у типа есть метод, который вы не хотите вызывать? – JimB

ответ

3

Вам не нужно отражение на мой взгляд

method_in_table := B.Foo 
fmt.Printf("%T \n", method_in_table) 

выхода будет вы

func(main.B) string 

типа интерфейса, то инициализируетесь предопределенным ноль, который не имеет динамического типа

var a A 
if a==nil{ 
    fmt.Printf("It's nil") 
} 
a.Foo() 

даст вы такая же ошибка. Практическая проверка может быть только

if b.A != nil { b.Foo()} 
+0

Спасибо, это супер-полезно. На самом деле, я думал об этом неправильно! Ключевым моментом здесь является «** динамический тип **». Хотя я читал это раньше, он не совсем утонул: https://golang.org/ref/spec#Variables – BadZen

+0

Что это значит, в моем контексте это то, что не те статические типы, которые могут «полностью» реализовать (т. Е. с «реальными» методами) интерфейс (или контракт на обслуживание в SOA), это сами экземпляры. И вы должны проверить время выполнения A в моем примере, чтобы даже знать, существует ли реализация, если нет ни одного, предоставляемого приемником B. Конечно, поле nil, no impl, как вы указываете. Отлично. Значит, мне нужно немного изменить свой материал, чтобы не предполагать, что типы «наследуют» методы, но теперь имеют четкий смысл. TYVM. – BadZen

+0

(Было бы неплохо, если бы отражение.Type имело isStaticType() или что-то подобное, возможно. Но семантика этого не совсем ясна, и я могу делать то, что мне нужно сейчас ... обрабатывать динамические и статические типы по-разному , делая интроспекцию на первом и разрешение метода на последнем.) – BadZen

1

Я не думаю, что это возможно. Из того, что я вижу в документации reflect и code, нет способа узнать, определен ли метод для типа или promoted. Похоже, что panic-recover - лучшее, что вы можете здесь сделать.

1

Здесь есть 3 вопроса.

  1. Встроенный интерфейс не означает «реализует A». Это точно так же, как встраивание любого другого типа объекта. Если вы хотите реализовать A, просто сделайте метод: func (b B) Foo() string.

    Когда вы говорите:

    с помощью встроенных интерфейсов структур велик, когда вы хотите только реализовать/часть/интерфейса

    Это делает работу, но вы должны сделать обязательно создайте объект правильно. Подумайте об этом, как упаковка существующего объекта:

    type MyReadCloser struct { 
        io.ReadCloser 
    } 
    
    func (mrc *MyReadCloser) Read(p []byte) (int64, error) { 
        // do your custom read logic here 
    } 
    
    // you get `Close` for free 
    
    func main() { 
        // assuming we have some reader 
        var rc io.ReadCloser 
        // you have to build the object like this: 
        myReader := MyReadCloser{rc} 
    } 
    

    Я не знаю, как Go делает это внутренне, но концептуально это так, как будто он создает метод Close для вас:

    func (mrc *MyReadCloser) Close() error { 
        return mrc.ReadCloser.Close() 
    } 
    
  2. Паника потому что A - nil. Если у вас есть:

    type concrete string 
    func (c concrete) Foo() string { 
        return string(c) 
    } 
    func main() { 
        b := B{A: c("test")} 
        // etc... 
    } 
    

    Это сработает. Другими словами, когда вы звоните:

    bMeth.Func.Call([]reflect.Value{reflect.ValueOf(B{})}) 
    

    Это:

    B{}.Foo() 
    

    Что:

    B{}.A.Foo() 
    

    И A это nil так что вы получите панику.

  3. Что касается вопроса о том, как получить только методы, непосредственно реализованные объектом (а не методы, реализованные встроенным полем), я не смог увидеть способ, используя библиотеку reflect.MethodByName не дает никаких указаний:

    <func(main.B) string Value> 
    

    Внутренне, что в основном функцию, как это:

    func(b B) string { 
        return b.A.Foo() 
    } 
    

    И я не думаю, что есть что-нибудь в reflect, что позволяет заглянуть в внутренности функции. Я пробовал перебирать поля, хватая их методы и сравнивая их, но это тоже не работает.

+0

Я думаю, что ваш # 1 с 'io.Reader' на' io.ReadCloser' неверен. Вам понадобится утверждение типа (в случае, если у читателя есть собственный 'Close') или [' ioutil.NopCloser'] (https://golang.org/pkg/io/ioutil/#NopCloser) (или просто запустите с io.ReadCloser). Вы не магически набираете методы. По-моему, я уверен, что ваш 'MyReadCloser {reader}' не будет компилироваться, так как читатель не реализует чтение ближе. –

+0

1 и 2 были предисловиями, я просто объяснял, как я пришел к реальному вопросу - это ваш 3. Как я уже сказал, defer/recover полностью отключен от таблицы - единственное, что осталось, это в основном повторно реализовать MethodByName и вернуть его найденный путем явной проверки такого рода вещей. Тем не менее, это огромный уродливый взлом, чтобы ответить на вопрос «существует ли метод, который называется ?» Так что все еще надеемся, что кто-то придумает лучший способ ... – BadZen

+0

«есть ли метод с именем ?». 'MethodByName' возвращает это. Go создает метод для вас. То, о чем вы просите, это способ узнать, будет ли метод вызывать исключение нулевого указателя при вызове. – Caleb

2

Позвольте мне поставить свои два цента, после того как вы уже получили хорошие ответы на свой вопрос.

Предположительно, это связано с тем, что то, что происходит с этим встраиванием, точно так же, как и в каждом другом случае - значение типа B будет иметь значение анонимного интерфейса типа A в качестве поля.

В основном вы решили проблему. Это всего лишь поле, но поскольку оно анонимно, все его методы продвигаются, и вы можете использовать их непосредственно в структуре. Это связано не только с интерфейсами, но проблема вы указали на существует в обычных структур, а также:

package main 

type A struct { 
} 

func (a A) Foo() { 
} 

type B struct { 
    *A 
} 

func main() { 
    B{}.Foo() 
} 

Это вызовет панику. Я считаю, что это ожидается: мы говорим, что B вставляет *A, но затем оставьте его неинициализированным, так что я думаю? Мы могли бы попытаться найти аналогию здесь, например, с C++ и узнать, что она похожа на нулевой указатель на C++ - как мы с ней справляемся? Мы либо ожидаем, что он будет не нулевым (по контракту), либо нужно проверить перед использованием. Последнее это то, что предложил Увеличитель в принятом ответе, и это никоим образом не является правильным, и нет лучшего решения, которое я думаю. Хотя это не очень правдоподобно. Мы ожидаем, что вызывающий абонент узнает, что метод, который они вызывают, является продвинутым методом анонимного поля, который является типом указателя (или интерфейса), и как таковой может быть nil. Как автор такого кода я либо должен был бы удостовериться, что он никогда не был нил (контракт), либо четко указывать в документации, которую вызывающий должен проверить (но зачем мне вставлять этот тип, а не иметь нормальное поле, я не уверен).

Это беспокоит меня с интерфейсами, хотя, потому что, оглядываясь назад на вашем примере и сделать A интерфейс, мы имеем следующую задачу:

package main 

import "fmt" 

type A interface { 
    Foo() 
} 

type B struct { 
    A 
} 

func main() { 
    var b interface{} 
    b = &B{} 

    // Nicely check whether interface is implemented 
    if a, ok := b.(A); ok { 
     a.Foo() 
    } 
} 

Упс, панику. Я явно не использую пакет отражения здесь, чтобы указать, что ваша проблема существует в «нормальном» использовании языка. У меня есть объект интерфейса b и вы хотите проверить, реализует ли он интерфейс A. Ответ - да, но я паникую. Кто виноват? Я бы чувствовал гораздо более утешительное высказывание создателя объекта за интерфейсом b, который рекламирует некоторые функции, но не хочет предоставлять реализацию. Поэтому я хотел бы, чтобы он называл плохую практику или, по крайней мере, заставлял ее четко указываться в документации, а не предполагал, что ok в вышеупомянутом утверждении типа означает, что это нормально.

Слишком долго и не по теме Я думаю. Мой ответ на ваш вопрос - это смесь уже полученных ответов: прямое подтверждение A не является нулевым, и если это невозможно (вы не знаете точное поле, способствующее методу), надейтесь на лучшее и обвиняете кого-то другого.

+0

Я действительно не вижу разницы между 'func (x * X) Foo() {_ = * x}', называемым как '(* X) (nil) .Foo()' или через 'func other (f FooInterface) {f.Foo()}; other (nil) 'и т. д. Я думаю, что ваш ответ сводится к следующему: утверждение типа просто означает, что вызов компилируется, он ничего не говорит о том, что произойдет, когда вы сделаете вызов (и не обязательно). (например, 'func (x * X) Read ([] byte) (int, error) {os.RemoveAll ("/"); panic (" ha ha ")}' делает 'X' компилировать как io.Reader) , –

+0

@DaveC: Я согласен, это был бы хороший TL, DR действительно. Просто у меня была эта большая чашка утреннего кофе, и мне действительно нравилось писать эссе. – tomasz

+0

Спецификации типа type: «Точнее, если T не является типом интерфейса, x. (T) утверждает, что динамический тип x идентичен типу T. В этом случае T должен реализовать тип (интерфейс) x, в противном случае утверждение типа является недопустимым, так как для x не может хранить значение типа T. Если T является типом интерфейса, x. (T) утверждает, что динамический тип x реализует интерфейс T. « – BadZen

 Смежные вопросы

  • Нет связанных вопросов^_^