Пустые интерфейсы в Голанге

Изменить: это неправильный способ использования интерфейсов в Go. Цель этого вопроса - понять, как в Go работают пустые интерфейсы.

Если все типы в Go реализуют interface{} (пустой интерфейс), почему я не могу получить доступ к полю name в структурах Cat и Dog? Как я могу получить доступ к полю имени каждой структуры с помощью функции sayHi ()?

package main

import (
    "fmt"
)

func sayHi(i interface{}) {

    fmt.Println(i, "says hello")

    // Not understanding this error message
    fmt.Println(i.name) //  i.name undefined (type interface {} is interface with no methods)
} 

type Dog struct{
    name string
}
type Cat struct{
    name string
}

func main() {
    d := Dog{"Sparky"}
    c := Cat{"Garfield"}

    sayHi(d) // {Sparky} says hello
    sayHi(c) // {Garfield} says hello
}

person Eug    schedule 29.01.2020    source источник
comment
После некоторого дальнейшего исследования я обнаружил, что это проходит через A Tour of Go. tour.golang.org/methods/15.   -  person Eug    schedule 30.01.2020
comment
Интерфейсы определяют поведение (методы чтения), а не данные.   -  person Volker    schedule 30.01.2020


Ответы (3)


interface{} - это набор методов, а не набор полей. Тип реализует интерфейс, если его методы включают методы этого интерфейса. Поскольку пустой интерфейс не имеет методов, его реализуют все типы.

Если вам нужно получить доступ к полю, вы должны получить исходный тип:

name, ok:=i.(Dog).name

Это восстановит имя, если i равно Dog.

В качестве альтернативы реализуйте функцию getName() для Dog и Cat, и тогда они оба будут реализовывать следующий интерфейс:

type NamedType interface {
   getName() string
}

Затем вы можете переписать свою функцию как:

func sayHi(i NamedType) {
   fmt.Println(i.getName()) 
}
person Burak Serdar    schedule 29.01.2020

Вы не можете этого сделать, потому что значения интерфейса этого не делают.

Что делают значения интерфейса - независимо от самого типа интерфейса; не имеет значения, является ли тип интерфейса пустым или нет - они содержат две вещи:

  • конкретный тип некоторого значения (или отсутствие типа); а также
  • значение этого конкретного типа (или отсутствие значения).

Итак, если какая-то переменная v или выражение e имеет тип I, где I - это тип интерфейса, то вы можете с помощью некоторого синтаксиса проверить одно или оба из этих двух «полей». Это не struct поля, поэтому вы не можете просто использовать v.type, но вы можете сделать следующее:

switch v.(type) {
case int: // the value in v has type int
case *float64: // the value in v has type float64
// etc
}

.(type) в switch означает позвольте мне взглянуть на поле типа.

Получить фактическое значение сложнее, потому что Go более или менее требует, чтобы вы сначала проверили тип. В вашем случае вы знаете, что i содержит либо Dog, либо Cat, поэтому вы можете написать:

var name string
switch i.(type) {
case Dog: name = i.(Dog).name
case Cat: name = i.(Cat).name
default: panic("whatever 'i' is, it is not a Dog or Cat")
}
fmt.Println(name)

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

Что ж, иногда есть шаг перед первым шагом: выяснить, есть ли в переменной что-нибудь вообще. Вы делаете это с помощью:

if i == nil {
    ...
}

Однако обратите внимание, что если i имеет какое-то типизированное значение и тип может содержать нулевые указатели, часть value i может быть нулевой, а i == nil будет ложью . Это потому, что в i есть тип.

var i interface{}
var p *int
if i == nil {
    fmt.Println("i is initially nil")
}
if p == nil {
    fmt.Println("p is nil")
}
i = p
if i != nil {
    fmt.Printf("i is now not nil, even though i.(*int) is %v\n", i.(*int))
}

(попробуйте это на игровой площадке).

Обычно это неправильный способ использования interface

Чаще всего - есть исключения - мы даже не пытаемся посмотреть на тип какого-либо интерфейса. Вместо этого мы определяем интерфейс, который предоставляет методы - функции, которые мы можем вызывать, - которые делают то, что нам нужно. См. ответ Бурака Сердара, в котором тип интерфейса имеет getName метод. Затем, вместо того, чтобы пытаться выяснить, какой из ограниченного набора типов нам кто-то дал, мы просто говорим:

name := i.getName()

для вызова getName метода для базового конкретного значения. Если i содержит Dog, это вызывает func (Dog) getName() string, который вам необходимо определить. Если i содержит Cat, он вызывает func (Cat) getName() string. Если вы решите добавить в свою коллекцию тип с именем Bird, вы можете определить func (Bird) getName() string и так далее.

(Обычно методы также экспортируются: GetName, а не getName.)

person torek    schedule 29.01.2020

Как вы сказали, interface{} - это пустой интерфейс. Как вы можете предположить, что что-то «пустое» содержит поле name (fmt.Println(i.name))? Вы не можете. Фактически, go не поддерживает поля в интерфейсах, только методы.

Что вы можете сделать (и есть, конечно, много решений), так это определить интерфейс (назовем его Pet), который имеет метод, возвращающий имя питомца:

type Pet interface {
    getName() string
}

Затем вы можете получить этот интерфейс (его объект) в функции sayHi и использовать его для печати имени питомца:

func sayHi(i Pet) {
    fmt.Println(i.getName())
}

Теперь, чтобы иметь возможность передавать Dog или Cat в sayHi(), обе эти структуры должны реализовать интерфейс. Итак, определите для них getName() методы:

func (d Dog) getName() string {
    return d.name
}

func (c Cat) getName() string {
    return c.name
}

Вот и все.

person Martin Heralecký    schedule 29.01.2020