Znaczenie struktury z wbudowanym anonimowym interfejsem?

87

sort pakiet:

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

...

type reverse struct {
    Interface
}

Jakie jest znaczenie anonimowego interfejsu Interfacew strukturze reverse?

warvariuc
źródło
Dla poszukiwaczy istnieje znacznie prostsze wyjaśnienie: Bliższe spojrzenie na Golang z perspektywy architekta . Nie pozwól, aby tytuł artykułu Cię odstraszył. :)
7stud
10
AIUI, ten artykuł ("Bliższe spojrzenie ...") tak naprawdę nie mówi o tym, co to znaczy osadzać anonimowe interfejsy w strukturze, tylko ogólnie mówi o interfejsach.
Adrian Ludwin

Odpowiedzi:

67

W ten sposób reverse implementuje sort.Interfacei możemy przesłonić określoną metodę bez konieczności definiowania wszystkich innych

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

Zwróć uwagę, jak tutaj zamienia się (j,i)zamiast, (i,j)a także jest to jedyna metoda zadeklarowana dla struktury, reversenawet jeśli reverseimplementujesort.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)
}

Cokolwiek struktura jest przekazywana w tej metodzie, konwertujemy ją na nową reversestrukturę.

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

Prawdziwa wartość pojawia się, jeśli pomyślisz, co byś musiał zrobić, gdyby takie podejście nie było możliwe.

  1. Dodaj inną Reversemetodę do sort.Interface?
  2. Utworzyć kolejny ReverseInterface?
  3. ...?

Każda z tych zmian wymagałaby o wiele więcej wierszy kodu w tysiącach pakietów, które chcą korzystać ze standardowej funkcji odwrotnej.

fabrizioM
źródło
2
więc pozwala na przedefiniowanie tylko niektórych metod interfejsu?
David 天宇 Wong
1
Ważną częścią jest to, że reversema typ członkaInterface . Ten element członkowski ma następnie metody wywoływane w zewnętrznej strukturze lub nadpisywane.
Bryan
Czy tę funkcję (lub podejście) można uznać za sposób na osiągnięcie tego, co robimy w Javie przez. extenddo rozszerzania nieabstrakcyjnych podklas? Dla mnie może to być wygodny sposób na zastąpienie tylko niektórych metod przy użyciu istniejących, które są implementowane przez wewnętrzną Interface.
Kevin Ghaboosi,
Więc to rodzaj spadku? I return r.Interface.Less(j, i)czy wywołuje implementację nadrzędną?
warvariuc
39

Ok, zaakceptowana odpowiedź pomogła mi zrozumieć, ale zdecydowałem się zamieścić wyjaśnienie, które moim zdaniem lepiej pasuje do mojego sposobu myślenia.

„Efektywne Go” ma przykład interfejsów mających osadzone innych interfejsów:

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

oraz struktura mająca osadzone inne struktury:

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

Ale nie ma wzmianki o strukturze mającej osadzony interfejs. Byłem zdezorientowany widząc to w sortpakiecie:

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

...

type reverse struct {
    Interface
}

Ale pomysł jest prosty. To prawie to samo, co:

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

metody IntSliceawansu reverse.

I to:

type reverse struct {
    Interface
}

oznacza, że sort.reversemoże osadzić dowolną strukturę, która implementuje interfejs sort.Interfacei wszelkie metody, które ma ten interfejs, zostaną promowane reverse.

sort.Interfacema metodę, Less(i, j int) boolktórą można teraz nadpisać:

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

Moje zamieszanie w zrozumieniu

type reverse struct {
    Interface
}

polegało na tym, że myślałem, że struktura zawsze ma ustaloną strukturę, czyli stałą liczbę pól o ustalonych typach.

Ale poniższe udowadniają, że się mylę:

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
źródło
3
Jeśli rozumiem poprawnie, wartości interfejsu są reprezentowane przez wskaźnik do przypisanej do niego instancji oraz wskaźnik do tabeli metod typu instancji. Zatem wszystkie wartości interfejsu mają tę samą strukturę w pamięci. Strukturalnie osadzanie jest tym samym, co kompozycja. Zatem nawet struktura osadzająca interfejs miałaby stałą strukturę. Struktury instancji wskazywanych przez interfejs będą się różnić.
Nishant George Agrwal
Uznałem, że jest to lepsza odpowiedź niż zaakceptowana, ponieważ zawierała znacznie więcej szczegółów, jasny przykład i link do dokumentacji.
110100100
25

Wyrok

type reverse struct {
    Interface
}

umożliwia zainicjowanie reversewszystkiego, co implementuje interfejs Interface. Przykład:

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

W ten sposób wszystkie metody implementowane przez Interfacewartość osadzoną są wypełniane na zewnątrz, podczas gdy nadal możesz zastąpić niektóre z nich reverse, na przykład w Lesscelu odwrócenia sortowania.

Tak właśnie się dzieje, gdy używasz sort.Reverse. Możesz przeczytać o osadzaniu w sekcji struct specyfikacji .

nemo
źródło
5

Podam również moje wyjaśnienie. sortPakiet określa unexported typu reverse, który jest struktura, osadzającej Interface.

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

Pozwala to Reverse na użycie metod innej implementacji interfejsu. Jest to tak zwane composition, co jest potężną funkcją Go.

LessMetoda reversepołączeń Systemy Lessmetoda wbudowanego Interfacewartości, ale z indeksami odwrócenie, odwrócenie kolejności sortowania wyników.

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

Lena Swappozostałe dwie metody reversesą niejawnie dostarczane przez oryginalną Interfacewartość, ponieważ jest to pole osadzone. Wyeksportowana Reversefunkcja zwraca instancję reversetypu, który zawiera oryginalną Interfacewartość.

// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
    return &reverse{data}
}
Endre Simo
źródło
Dla mnie wygląda to na dziedziczenie. „ LessMetoda reversewywołuje Lessmetodę Interfacewartości osadzonej , ale z odwróconymi indeksami, odwraca kolejność wyników sortowania”. - wygląda to na wywołanie implementacji nadrzędnej.
warvariuc
Tak długo, jak typ reverse ma tylko jedno pole, które implementuje interfejs interfejsu, staje się również członkiem interfejsu interfejsu: 0
Allan Guwatudde
1

Uważam, że ta funkcja jest bardzo pomocna podczas pisania makiet w testach .

Oto taki przykład:

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

Używając:

type storeMock struct {
    Store
    ...
}

Nie trzeba kpić ze wszystkich Storemetod. HealthCheckMożna tylko wyśmiać, ponieważ w TestIsHealthyteście jest używana tylko ta metoda .

Poniżej wynik testpolecenia:

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

Prawdziwym przykładem świat tego użyć jednego przypadku można odnaleźć podczas testowania AWS SDK .


Aby było to jeszcze bardziej oczywiste, oto brzydka alternatywa - minimum, które należy zaimplementować, aby zadowolić Storeinterfejs:

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
źródło