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?
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?
Risposte:
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.
Reversemetodo al sort.Interface?Qualsiasi di questa modifica richiederebbe molte molte più righe di codice su migliaia di pacchetti che desiderano utilizzare la funzionalità inversa standard.
reverseha un membro di tipo Interface. Questo membro ha quindi i suoi metodi richiamabili sulla struttura esterna o sovrascrivibili.
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.
return r.Interface.Less(j, i)sta chiamando l'implementazione genitore?
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).
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"})
}
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 .
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).
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.
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}
}
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.
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
}