Significato di una struttura con interfaccia anonima incorporata?


89

sort pacchetto:

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

...

type reverse struct {
    Interface
}

Qual è il significato dell'interfaccia anonima Interfacein struct reverse?


Per i ricercatori, c'è una spiegazione molto più semplice qui: Uno sguardo più da vicino a Golang dal punto di vista di un architetto . Non lasciare che il titolo dell'articolo ti spaventi. :)
7stud

10
AIUI, quell'articolo ("A Closer Look ...") in realtà non parla di cosa significhi incorporare interfacce anonime in una struttura, parla solo di interfacce in generale.
Adrian Ludwin

Risposte:


73

In questo modo reverse implementa il sort.Interfacee possiamo sovrascrivere un metodo specifico senza dover definire tutti gli altri

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

Si noti come qui si scambia (j,i)invece di (i,j)e anche questo è l'unico metodo dichiarato per la struttura reverseanche se reverseimplementatosort.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)
}

Qualunque sia la struttura passata all'interno di questo metodo, la convertiamo in una nuova reversestruttura.

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

Il vero valore arriva se pensi a cosa dovresti fare se questo approccio non fosse possibile.

  1. Aggiungi un altro Reversemetodo al sort.Interface?
  2. Creare un'altra ReverseInterface?
  3. ...?

Qualsiasi di questa modifica richiederebbe molte molte più righe di codice su migliaia di pacchetti che desiderano utilizzare la funzionalità inversa standard.


3
quindi ti permette di ridefinire solo alcuni dei metodi di un'interfaccia?
David 天宇 Wong

2
La parte importante è che reverseha un membro di tipo Interface. Questo membro ha quindi i suoi metodi richiamabili sulla struttura esterna o sovrascrivibili.
Bryan

Questa caratteristica (o approccio) potrebbe essere considerata come un modo per ottenere ciò che facciamo in Java attraverso. extendper estendere sottoclassi non astratte? Per me, questo può essere un modo pratico per sovrascrivere solo alcuni metodi mentre si utilizzano quelli esistenti implementati da internal Interface.
Kevin Ghaboosi

Quindi è una specie di eredità? E return r.Interface.Less(j, i)sta chiamando l'implementazione genitore?
warvariuc

Già la seconda volta mi confondo su questo. Tutte le risposte sembrano dimenticare l'uso intuitivo di questa struct (che è necessaria per realtà sorta nulla): sort.Sort(sort.Reverse(sort.IntSlice(example))). Per me il punto dolente qui: il metodo Sort viene promosso alla struttura inversa, ma la chiamata è in stile non membro (destinatario).
seebi

41

Ok, la risposta accettata mi ha aiutato a capire, ma ho deciso di postare una spiegazione che secondo me si adatta meglio al mio modo di pensare.

La "efficace Go" ha esempio di interfacce con altre interfacce incorporate:

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

e una struttura che ha incorporato altre strutture:

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

Ma non si fa menzione di una struttura che abbia incorporato un'interfaccia. Ero confuso vedendo questo nel sortpacchetto:

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

...

type reverse struct {
    Interface
}

Ma l'idea è semplice. È quasi uguale a:

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

metodi per IntSliceessere promossi a reverse.

E questo:

type reverse struct {
    Interface
}

significa che sort.reverse può incorporare qualsiasi struttura che implementi l'interfaccia sort.Interfacee qualunque metodo abbia l'interfaccia, saranno promossi reverse.

sort.Interface ha metodo Less(i, j int) bool che ora può essere sovrascritto:

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

La mia confusione nella comprensione

type reverse struct {
    Interface
}

era che pensavo che una struttura avesse sempre una struttura fissa, cioè un numero fisso di campi di tipi fissi.

Ma quanto segue mi dimostra che ho torto:

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

3
Se la mia comprensione è corretta, i valori dell'interfaccia sono rappresentati da un puntatore all'istanza assegnatagli e un puntatore alla tabella dei metodi del tipo di istanza. Quindi tutti i valori dell'interfaccia hanno la stessa struttura in memoria. Strutturalmente, l'incorporamento è lo stesso della composizione. Quindi anche una struttura che incorpora un'interfaccia avrebbe una struttura fissa. Le strutture delle istanze a cui punta l'interfaccia saranno diverse.
Nishant George Agrwal

Ho trovato questa una risposta migliore di quella accettata in quanto fornisce molti più dettagli, un chiaro esempio e un collegamento alla documentazione.
110100100

28

La dichiarazione

type reverse struct {
    Interface
}

consente di inizializzare reversecon tutto ciò che implementa l'interfaccia Interface. Esempio:

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

In questo modo, tutti i metodi implementati dal Interfacevalore incorporato vengono popolati all'esterno mentre sei ancora in grado di sovrascriverne alcuni in reverse, ad esempioLess per invertire l'ordinamento.

Questo è ciò che accade effettivamente quando usi sort.Reverse. Puoi leggere informazioni sull'incorporamento nella sezione struct delle specifiche .


Già la seconda volta mi confondo su questo. Tutte le risposte sembrano dimenticare l'uso intuitivo di questa struct (che è necessaria per realtà sorta nulla): sort.Sort(sort.Reverse(sort.IntSlice(example))). Per me il punto dolente qui: il metodo Sort viene promosso alla struttura inversa, ma la chiamata è in stile non membro (destinatario).
seebi

@seebi se questa è una domanda non capisco, mi dispiace. Inoltre il Sortmetodo non è promosso, richiede qualcosa che soddisfi sort.Interfacee il contrario è una cosa del genere, cambia solo i metodi rilevanti dei suoi sort.Interfacedati incorporati in modo che l'ordinamento risultante sia invertito.
nemo

non una domanda, solo un'osservazione, hai ragione, intendevo i metodi Less, Swap, Len!
seebi

7

Darò anche la mia spiegazione. Il sortpacchetto definisce un tipo non esportato reverse, che è una struttura, che incorpora Interface.

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

Ciò consente a Reverse di utilizzare i metodi di un'altra implementazione dell'interfaccia. Questo è il cosiddettocomposition , che è una potente funzionalità di Go.

Il Lessmetodo per reversechiama il Lessmetodo del Interfacevalore incorporato , ma con gli indici invertiti, invertendo l'ordine dei risultati dell'ordinamento.

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

Len e Swap gli altri due metodi di reverse, sono forniti implicitamente dal Interfacevalore originale perché è un campo incorporato. La Reversefunzione esportata restituisce un'istanza del reversetipo che contiene il Interfacevalore originale .

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

A me questo sembra eredità. "Il Lessmetodo per reversechiama il Lessmetodo del Interfacevalore incorporato , ma con gli indici invertiti, invertendo l'ordine dei risultati dell'ordinamento." - questo sembra chiamare l'implementazione genitore.
warvariuc

Finché il tipo reverse ha un solo campo che implementa l'interfaccia Interface diventa anche un membro dell'interfaccia Interface: 0
Allan Guwatudde

1

Trovo questa funzione molto utile durante la scrittura di mock durante i test .

Ecco un esempio del genere:

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
    ...
}

Non è necessario prendere in giro tutti i Storemetodi. Solo HealthCheckpuò essere deriso, poiché solo questo metodo viene utilizzato inTestIsHealthy test .

Di seguito il risultato del testcomando:

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

Un esempio del mondo reale di questo caso d'uso che è possibile trovare durante il test dell'SDK AWS .


Per renderlo ancora più ovvio, ecco la brutta alternativa, quella minima che deve essere implementata per soddisfare l' Storeinterfaccia:

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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.