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.funcReverse(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.
Fügen Sie Reversedem sort.Interface? Eine weitere Methode hinzu .
Ein anderes ReverseInterface erstellen?
...?
Jede dieser Änderungen würde viel mehr Codezeilen in Tausenden von Paketen erfordern, die die Standard-Reverse-Funktionalität verwenden möchten.
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 interfacetype Stringer interface {
String() string
}
// a struct that implements Stringer interfacetype Struct1 struct {
field1 string
}
func(s Struct1)String()string {
return s.field1
}
// another struct that implements Stringer interface, but has a different set of fieldstype 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 interfacetype StringerContainer struct {
Stringer
}
funcmain() {
// 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"})
}
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.
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.funcReverse(data Interface)Interface {
return &reverse{data}
}
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 exampletype Item struct {
First, Last string
}
// Store abstracts the DB storetype Store interface {
Create(string, string) (*Item, error)
GetByID(string) (*Item, error)
Update(*Item) error
HealthCheck() error
Close() error
}
// this is a mock implementing Store interfacetype storeMock struct {
Store
// healthy is false by default
healthy bool
}
// HealthCheck is mocked functionfunc(s *storeMock)HealthCheck()error {
if !s.healthy {
return fmt.Errorf("mock error")
}
returnnil
}
// IsHealthy is the tested functionfuncIsHealthy(s Store)bool {
return s.HealthCheck() == nil
}
funcTestIsHealthy(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 :
Antworten:
Auf diese Weise implementiert reverse das
sort.Interface
und wir können eine bestimmte Methode überschreiben, ohne alle anderen definieren zu müssentype 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,reverse
selbst wenn siereverse
implementiert 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
reverse
Struktur.// 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.
Reverse
demsort.Interface
? Eine weitere Methode hinzu .Jede dieser Änderungen würde viel mehr Codezeilen in Tausenden von Paketen erfordern, die die Standard-Reverse-Funktionalität verwenden möchten.
quelle
reverse
ein Mitglied vom Typ hatInterface
. Dieses Mitglied hat dann seine Methoden auf der äußeren Struktur aufrufbar oder überschreibbar.extend
zur 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 werdenInterface
.return r.Interface.Less(j, i)
ruft die übergeordnete Implementierung auf?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
sort
Paket 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
IntSlice
Beförderung zureverse
.Und das:
type reverse struct { Interface }
bedeutet, dass
sort.reverse
jede Struktur, die die Schnittstelle implementiert,sort.Interface
und alle Methoden, über die die Schnittstelle verfügt, eingebettet werden könnenreverse
.sort.Interface
hat Methode,Less(i, j int) bool
die 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"}) }
quelle
Die Aussage
type reverse struct { Interface }
ermöglicht es Ihnen,
reverse
mit allem zu initialisieren, was die Schnittstelle implementiertInterface
. Beispiel:&reverse{sort.Intslice([]int{1,2,3})}
Auf diese Weise werden alle vom eingebetteten
Interface
Wert implementierten Methoden nach außen gefüllt, während Sie noch einige davon überschreiben könnenreverse
, um beispielsweiseLess
die Sortierung umzukehren.Dies ist, was tatsächlich passiert, wenn Sie verwenden
sort.Reverse
. Informationen zum Einbetten finden Sie im Abschnitt struct der Spezifikation .quelle
Ich werde auch meine Erklärung geben. Das
sort
Paket definiert einen nicht exportierten Typreverse
, bei dem es sich um eine Struktur handelt, die eingebettet wirdInterface
.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
Less
Methode fürreverse
ruft dieLess
Methode des eingebettetenInterface
Werts 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) }
Len
undSwap
die anderen beiden Methoden vonreverse
werden implizit durch den ursprünglichenInterface
Wert bereitgestellt , da es sich um ein eingebettetes Feld handelt. Die exportierteReverse
Funktion gibt eine Instanz desreverse
Typs zurück, der den ursprünglichenInterface
Wert enthält .// Reverse returns the reverse order for data. func Reverse(data Interface) Interface { return &reverse{data} }
quelle
Less
Methode fürreverse
ruft dieLess
Methode des eingebettetenInterface
Werts auf, wobei jedoch die Indizes umgedreht werden und die Reihenfolge der Sortierergebnisse umgekehrt wird." - Dies sieht aus wie ein Aufruf der übergeordneten Implementierung.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
Store
Methoden verspotten . NurHealthCheck
kann verspottet werden, da nur diese Methode imTestIsHealthy
Test verwendet wird.Unter dem Ergebnis des
test
Befehls:$ 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
Store
Schnittstelle 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 }
quelle