Пустые интерфейсы в Golang - PullRequest
0 голосов
/ 30 января 2020

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

Если все типы в Go реализуют interface{} (пустой интерфейс), почему я не могу получить доступ к * 1008? * поля в структурах 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
}

Ответы [ 3 ]

3 голосов
/ 30 января 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 имеет некоторое набранное значение, и тип может содержать ноль указатели, значение часть 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))
}

(попробуйте это на Go детской площадке ).

Обычно это неправильный способ использования 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.)

3 голосов
/ 30 января 2020

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

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

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

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

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

type NamedType interface {
   getName() string
}

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

func sayHi(i NamedType) {
   fmt.Println(i.getName()) 
}
1 голос
/ 30 января 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
}

И все.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...