Significado de uma estrutura com interface anônima incorporada?

87

sort pacote:

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

...

type reverse struct {
    Interface
}

Qual é o significado de interface anônima Interfaceem struct reverse?

Warvariuc
fonte
Para os pesquisadores, há uma explicação muito mais simples aqui: Um exame mais detalhado de Golang da perspectiva de um arquiteto . Não deixe o título do artigo assustar você. :)
7 de
10
AIUI, aquele artigo ("A Closer Look ...") não fala realmente sobre o que significa embutir interfaces anônimas em uma estrutura, ele apenas fala sobre interfaces em geral.
Adrian Ludwin

Respostas:

67

Desta forma, o reverso implementa o sort.Interfacee podemos substituir um método específico sem ter que definir todos os outros

type reverse struct {
        // This embedded Interface permits Reverse to use the methods of
        // another Interface implementation.
        Interface
}

Observe como aqui ele troca em (j,i)vez de (i,j)e também este é o único método declarado para a estrutura, reversemesmo se reverseimplementarsort.Interface

// Less returns the opposite of the embedded implementation's Less method.
func (r reverse) Less(i, j int) bool {
        return r.Interface.Less(j, i)
}

Qualquer estrutura passada dentro deste método, nós a convertemos em uma nova reverseestrutura.

// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
        return &reverse{data}
}

O valor real surge se você pensar no que teria de fazer se essa abordagem não fosse possível.

  1. Adicione outro Reversemétodo ao sort.Interface?
  2. Criar outra ReverseInterface?
  3. ...?

Qualquer uma dessas mudanças exigiria muito mais linhas de código em milhares de pacotes que desejam usar a funcionalidade reversa padrão.

fabrizioM
fonte
2
então permite redefinir apenas alguns dos métodos de uma interface?
David 天宇 Wong
1
O importante é que reversetenha um membro do tipo Interface. Este membro então tem seus métodos chamáveis ​​na estrutura externa ou substituíveis.
Bryan
Poderia este recurso (ou abordagem) ser considerado uma forma de conseguir o que fazemos em Java através. extendpara estender subclasses não abstratas? Para mim, essa pode ser uma maneira útil de substituir apenas certos métodos ao usar os existentes que são implementados por interno Interface.
Kevin Ghaboosi
Então é uma espécie de herança? E return r.Interface.Less(j, i)está chamando a implementação pai?
warvariuc de
39

Ok, a resposta aceita me ajudou a entender, mas resolvi postar uma explicação que acho que se adequa melhor ao meu pensamento.

O "Effective Go" tem exemplos de interfaces com outras interfaces incorporadas:

// ReadWriter is the interface that combines the Reader and Writer interfaces.
type ReadWriter interface {
    Reader
    Writer
}

e uma estrutura tendo outras estruturas incorporadas:

// ReadWriter stores pointers to a Reader and a Writer.
// It implements io.ReadWriter.
type ReadWriter struct {
    *Reader  // *bufio.Reader
    *Writer  // *bufio.Writer
}

Mas não há menção de um struct ter embutido uma interface. Fiquei confuso ao ver isso no sortpacote:

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

...

type reverse struct {
    Interface
}

Mas a ideia é simples. É quase o mesmo que:

type reverse struct {
    IntSlice  // IntSlice struct attaches the methods of Interface to []int, sorting in increasing order
}

métodos de IntSlicepromoção reverse.

E isto:

type reverse struct {
    Interface
}

significa que sort.reversepode incorporar qualquer estrutura que implemente interface sort.Interfacee quaisquer métodos que a interface possua, eles serão promovidos reverse.

sort.Interfacetem método Less(i, j int) boolque agora pode ser substituído:

// Less returns the opposite of the embedded implementation's Less method.
func (r reverse) Less(i, j int) bool {
    return r.Interface.Less(j, i)
}

Minha confusão no entendimento

type reverse struct {
    Interface
}

foi que pensei que uma estrutura sempre tem estrutura fixa, ou seja, número fixo de campos de tipos fixos.

Mas o seguinte prova que estou errado:

package main

import "fmt"

// some interface
type Stringer interface {
    String() string
}

// a struct that implements Stringer interface
type Struct1 struct {
    field1 string
}

func (s Struct1) String() string {
    return s.field1
}


// another struct that implements Stringer interface, but has a different set of fields
type Struct2 struct {
    field1 []string
    dummy bool
}

func (s Struct2) String() string {
    return fmt.Sprintf("%v, %v", s.field1, s.dummy)
}


// container that can embedd any struct which implements Stringer interface
type StringerContainer struct {
    Stringer
}


func main() {
    // the following prints: This is Struct1
    fmt.Println(StringerContainer{Struct1{"This is Struct1"}})
    // the following prints: [This is Struct1], true
    fmt.Println(StringerContainer{Struct2{[]string{"This", "is", "Struct1"}, true}})
    // the following does not compile:
    // cannot use "This is a type that does not implement Stringer" (type string)
    // as type Stringer in field value:
    // string does not implement Stringer (missing String method)
    fmt.Println(StringerContainer{"This is a type that does not implement Stringer"})
}
Warvariuc
fonte
3
Se meu entendimento estiver correto, os valores da interface são representados por um ponteiro para a instância atribuída a ela e um ponteiro para a tabela de métodos do tipo da instância. Portanto, todos os valores de interface têm a mesma estrutura na memória. Estruturalmente, a incorporação é o mesmo que a composição. Portanto, mesmo uma estrutura incorporando uma interface teria uma estrutura fixa. As estruturas das instâncias para as quais a interface aponta serão diferentes.
Nishant George Agrwal
Achei esta uma resposta melhor do que a aceita, pois deu muito mais detalhes, um exemplo claro e um link para a documentação.
110100100
25

A declaração

type reverse struct {
    Interface
}

permite que você inicialize reversecom tudo o que implementa a interface Interface. Exemplo:

&reverse{sort.Intslice([]int{1,2,3})}

Dessa forma, todos os métodos implementados pelo Interfacevalor incorporado são populados externamente enquanto você ainda é capaz de substituir alguns deles reverse, por exemplo, Lesspara reverter a classificação.

Isso é o que realmente acontece quando você usa sort.Reverse. Você pode ler sobre a incorporação na seção de estrutura da especificação .

nemo
fonte
5

Eu darei minha explicação também. O sortpacote define um tipo não exportado reverse, que é uma estrutura que incorpora Interface.

type reverse struct {
    // This embedded Interface permits Reverse to use the methods of
    // another Interface implementation.
    Interface
}

Isso permite que o Reverse use os métodos de outra implementação de interface. Este é o assim chamado composition, que é um recurso poderoso do Go.

O Lessmétodo para reversechama o Lessmétodo do Interfacevalor incorporado , mas com os índices invertidos, invertendo a ordem dos resultados da classificação.

// Less returns the opposite of the embedded implementation's Less method.
func (r reverse) Less(i, j int) bool {
    return r.Interface.Less(j, i)
}

Lene Swapos outros dois métodos de reverse, são fornecidos implicitamente pelo Interfacevalor original porque é um campo incorporado. A Reversefunção exportada retorna uma instância do reversetipo que contém o Interfacevalor original .

// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
    return &reverse{data}
}
Endre Simo
fonte
Para mim, isso parece herança. "O Lessmétodo para reversechama o Lessmétodo do Interfacevalor incorporado , mas com os índices invertidos, invertendo a ordem dos resultados da classificação." - isso se parece com a chamada da implementação pai.
warvariuc de
Enquanto type reverse tiver apenas um campo que implementa a interface Interface, ele também se tornará um membro da interface: 0
Allan Guwatudde
1

Acho esse recurso muito útil ao escrever simulações em testes .

Aqui está um exemplo:

package main_test

import (
    "fmt"
    "testing"
)

// Item represents the entity retrieved from the store
// It's not relevant in this example
type Item struct {
    First, Last string
}

// Store abstracts the DB store
type Store interface {
    Create(string, string) (*Item, error)
    GetByID(string) (*Item, error)
    Update(*Item) error
    HealthCheck() error
    Close() error
}

// this is a mock implementing Store interface
type storeMock struct {
    Store
    // healthy is false by default
    healthy bool
}

// HealthCheck is mocked function
func (s *storeMock) HealthCheck() error {
    if !s.healthy {
        return fmt.Errorf("mock error")
    }
    return nil
}

// IsHealthy is the tested function
func IsHealthy(s Store) bool {
    return s.HealthCheck() == nil
}

func TestIsHealthy(t *testing.T) {
    mock := &storeMock{}
    if IsHealthy(mock) {
        t.Errorf("IsHealthy should return false")
    }

    mock = &storeMock{healthy: true}
    if !IsHealthy(mock) {
        t.Errorf("IsHealthy should return true")
    }
}

Usando:

type storeMock struct {
    Store
    ...
}

Não é necessário zombar de todos os Storemétodos. Só HealthCheckpode ser simulado, já que apenas esse método é usado no TestIsHealthyteste.

Abaixo o resultado do testcomando:

$ go test -run '^TestIsHealthy$' ./main_test.go           
ok      command-line-arguments  0.003s

Um exemplo do mundo real desse caso de uso pode ser encontrado ao testar o SDK da AWS .


Para tornar ainda mais óbvio, aqui está a alternativa desagradável - o mínimo que se precisa implementar para satisfazer a Storeinterface:

type storeMock struct {
    healthy bool
}

func (s *storeMock) Create(a, b string) (i *Item, err error) {
    return
}
func (s *storeMock) GetByID(a string) (i *Item, err error) {
    return
}
func (s *storeMock) Update(i *Item) (err error) {
    return
}

// HealthCheck is mocked function
func (s *storeMock) HealthCheck() error {
    if !s.healthy {
        return fmt.Errorf("mock error")
    }
    return nil
}

func (s *storeMock) Close() (err error) {
    return
}
Czerasz
fonte