sort
Paket:
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
...
type reverse struct {
Interface
}
Was bedeutet anonyme Schnittstelle Interface
in struct reverse
?
sort
Paket:
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
...
type reverse struct {
Interface
}
Was bedeutet anonyme Schnittstelle Interface
in struct reverse
?
Antworten:
Auf diese Weise implementiert reverse das sort.Interface
und 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, reverse
selbst wenn sie reverse
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
dem sort.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.
reverse
ein Mitglied vom Typ hat Interface
. 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 werden Interface
.
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 zu reverse
.
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önnen reverse
.
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"})
}
Die Aussage
type reverse struct {
Interface
}
ermöglicht es Ihnen, reverse
mit allem zu initialisieren, was die Schnittstelle implementiert Interface
. 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önnen reverse
, um beispielsweise Less
die Sortierung umzukehren.
Dies ist, was tatsächlich passiert, wenn Sie verwenden sort.Reverse
. Informationen zum Einbetten finden Sie im Abschnitt struct der Spezifikation .
Ich werde auch meine Erklärung geben. Das sort
Paket 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 Less
Methode für reverse
ruft die Less
Methode des eingebetteten Interface
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
und Swap
die anderen beiden Methoden von reverse
werden implizit durch den ursprünglichen Interface
Wert bereitgestellt , da es sich um ein eingebettetes Feld handelt. Die exportierte Reverse
Funktion gibt eine Instanz des reverse
Typs zurück, der den ursprünglichen Interface
Wert enthält .
// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
return &reverse{data}
}
Less
Methode für reverse
ruft die Less
Methode des eingebetteten Interface
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 . Nur HealthCheck
kann verspottet werden, da nur diese Methode im TestIsHealthy
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
}