Bedeutung einer Struktur mit eingebetteter anonymer Schnittstelle?

87

sort Paket:

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

...

type reverse struct {
    Interface
}

Was bedeutet anonyme Schnittstelle Interfacein struct reverse?

warvariuc
quelle
Für Suchende gibt es hier eine viel einfachere Erklärung: Ein genauerer Blick auf Golang aus der Sicht eines Architekten . Lassen Sie sich vom Titel des Artikels nicht abschrecken. :)
7stud
10
AIUI, dieser Artikel ("A Closer Look ...") spricht nicht darüber, was es bedeutet, anonyme Schnittstellen in eine Struktur einzubetten, sondern nur über Schnittstellen im Allgemeinen.
Adrian Ludwin

Antworten:

67

Auf diese Weise implementiert reverse das sort.Interfaceund wir können eine bestimmte Methode überschreiben, ohne alle anderen definieren zu müssen

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

Beachten Sie, wie hier (j,i)statt getauscht wird (i,j)und dies auch die einzige Methode ist, die für die Struktur deklariert wurde, reverseselbst wenn sie reverseimplementiert istsort.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)
}

Unabhängig davon, welche Struktur in dieser Methode übergeben wird, konvertieren wir sie in eine neue reverseStruktur.

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

Der wahre Wert ergibt sich, wenn Sie überlegen, was Sie tun müssten, wenn dieser Ansatz nicht möglich wäre.

  1. Fügen Sie Reversedem sort.Interface? Eine weitere Methode hinzu .
  2. Ein anderes ReverseInterface erstellen?
  3. ...?

Jede dieser Änderungen würde viel mehr Codezeilen in Tausenden von Paketen erfordern, die die Standard-Reverse-Funktionalität verwenden möchten.

fabrizioM
quelle
2
So können Sie nur einige der Methoden einer Schnittstelle neu definieren?
David 天宇 Wong
1
Der wichtige Teil ist, dass reverseein Mitglied vom Typ hat Interface. Dieses Mitglied hat dann seine Methoden auf der äußeren Struktur aufrufbar oder überschreibbar.
Bryan
Könnte diese Funktion (oder dieser Ansatz) als ein Weg angesehen werden, um das zu erreichen, was wir in Java tun? extendzur Erweiterung nicht abstrakter Unterklassen? Für mich kann dies eine praktische Möglichkeit sein, nur bestimmte Methoden zu überschreiben, während die vorhandenen Methoden verwendet werden, die von intern implementiert werden Interface.
Kevin Ghaboosi
Es ist also eine Art Vererbung? Und return r.Interface.Less(j, i)ruft die übergeordnete Implementierung auf?
Warvariuc
39

Ok, die akzeptierte Antwort hat mir geholfen zu verstehen, aber ich habe beschlossen, eine Erklärung zu veröffentlichen, die meiner Meinung nach besser zu meiner Denkweise passt.

Das "Effective Go" hat ein Beispiel für Schnittstellen, in die andere Schnittstellen eingebettet sind:

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

und eine Struktur, die andere Strukturen eingebettet hat:

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

Es wird jedoch nicht erwähnt, dass eine Struktur eine Schnittstelle eingebettet hat. Ich war verwirrt, als ich das im sortPaket sah:

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

...

type reverse struct {
    Interface
}

Aber die Idee ist einfach. Es ist fast das gleiche wie:

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

Methoden der IntSliceBeförderung zu reverse.

Und das:

type reverse struct {
    Interface
}

bedeutet, dass sort.reversejede Struktur, die die Schnittstelle implementiert, sort.Interfaceund alle Methoden, über die die Schnittstelle verfügt, eingebettet werden können reverse.

sort.Interfacehat Methode, Less(i, j int) booldie jetzt überschrieben werden kann:

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

Meine Verwirrung im Verständnis

type reverse struct {
    Interface
}

war, dass ich dachte, dass eine Struktur immer eine feste Struktur hat, dh eine feste Anzahl von Feldern fester Typen.

Aber das Folgende beweist, dass ich falsch liege:

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
quelle
3
Wenn mein Verständnis korrekt ist, werden die Schnittstellenwerte durch einen Zeiger auf die ihr zugewiesene Instanz und einen Zeiger auf die Methodentabelle des Instanztyps dargestellt. Alle Schnittstellenwerte haben also die gleiche Struktur im Speicher. Strukturell entspricht das Einbetten der Zusammensetzung. Selbst eine Struktur, die eine Schnittstelle einbettet, hätte eine feste Struktur. Die Strukturen der Instanzen, auf die die Schnittstelle verweist, sind unterschiedlich.
Nishant George Agrwal
Ich fand dies eine bessere Antwort als die akzeptierte, da sie weitaus detaillierter war, ein klares Beispiel und einen Link zur Dokumentation.
110100100
25

Die Aussage

type reverse struct {
    Interface
}

ermöglicht es Ihnen, reversemit allem zu initialisieren, was die Schnittstelle implementiert Interface. Beispiel:

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

Auf diese Weise werden alle vom eingebetteten InterfaceWert implementierten Methoden nach außen gefüllt, während Sie noch einige davon überschreiben können reverse, um beispielsweise Lessdie Sortierung umzukehren.

Dies ist, was tatsächlich passiert, wenn Sie verwenden sort.Reverse. Informationen zum Einbetten finden Sie im Abschnitt struct der Spezifikation .

nemo
quelle
5

Ich werde auch meine Erklärung geben. Das sortPaket definiert einen nicht exportierten Typ reverse, bei dem es sich um eine Struktur handelt, die eingebettet wird Interface.

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

Dadurch kann Reverse die Methoden einer anderen Schnittstellenimplementierung verwenden. Dies ist das sogenannte composition, was ein mächtiges Merkmal von Go ist.

Die LessMethode für reverseruft die LessMethode des eingebetteten InterfaceWerts auf, wobei jedoch die Indizes umgedreht werden und die Reihenfolge der Sortierergebnisse umgekehrt wird.

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

Lenund Swapdie anderen beiden Methoden von reversewerden implizit durch den ursprünglichen InterfaceWert bereitgestellt , da es sich um ein eingebettetes Feld handelt. Die exportierte ReverseFunktion gibt eine Instanz des reverseTyps zurück, der den ursprünglichen InterfaceWert enthält .

// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
    return &reverse{data}
}
Endre Simo
quelle
Für mich sieht das nach Vererbung aus. "Die LessMethode für reverseruft die LessMethode des eingebetteten InterfaceWerts auf, wobei jedoch die Indizes umgedreht werden und die Reihenfolge der Sortierergebnisse umgekehrt wird." - Dies sieht aus wie ein Aufruf der übergeordneten Implementierung.
Warvariuc
Solange der Typ reverse nur ein Feld hat, das die Schnittstellenschnittstelle implementiert, wird er auch Mitglied der Schnittstellenschnittstelle: 0
Allan Guwatudde
1

Ich finde diese Funktion sehr hilfreich beim Schreiben von Mocks in Tests .

Hier ist ein solches Beispiel:

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

Durch die Nutzung:

type storeMock struct {
    Store
    ...
}

Man muss nicht alle StoreMethoden verspotten . Nur HealthCheckkann verspottet werden, da nur diese Methode im TestIsHealthyTest verwendet wird.

Unter dem Ergebnis des testBefehls:

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

Ein reales Beispiel für diesen Anwendungsfall finden Sie beim Testen des AWS SDK .


Um es noch offensichtlicher zu machen, hier ist die hässliche Alternative - das Minimum, das implementiert werden muss, um die StoreSchnittstelle zu erfüllen :

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
quelle