Значение структуры со встроенным анонимным интерфейсом?

87

sort пакет:

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

...

type reverse struct {
    Interface
}

В чем смысл анонимного интерфейса Interfaceв структуре reverse?

Варвариук
источник
Для поисковиков здесь есть гораздо более простое объяснение: более пристальный взгляд на Голанг с точки зрения архитектора . Не позволяйте названию статьи вас отпугнуть. :)
7stud
10
AIUI, эта статья («Внимательный взгляд…») на самом деле не говорит о том, что значит встраивать анонимные интерфейсы в структуру, она просто говорит об интерфейсах в целом.
Адриан Людвин

Ответы:

67

Таким образом, reverse реализует, sort.Interfaceи мы можем переопределить конкретный метод без необходимости определять все остальные.

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

Обратите внимание , как здесь она меняет местами (j,i)вместо (i,j)а также это единственный метод , объявленный для структуры , reverseдаже если reverseреализоватьsort.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)
}

Какую бы структуру ни передали внутри этого метода, мы преобразуем ее в новую reverseструктуру.

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

Настоящая ценность приходит, если вы думаете, что бы вы сделали, если бы такой подход был невозможен.

  1. Добавить еще один Reverseметод в sort.Interface?
  2. Создать еще один ReverseInterface?
  3. ...?

Любое из этих изменений потребует намного большего количества строк кода в тысячах пакетов, которые хотят использовать стандартные функции обратного преобразования.

фабрициоМ
источник
2
Значит, это позволяет вам переопределить только некоторые методы интерфейса?
Дэвид 天宇 Вонг
1
Важная часть - reverseэто член типа Interface. Затем у этого члена есть методы, вызываемые во внешней структуре или переопределяемые.
Брайан
Может ли эта функция (или подход) рассматриваться как способ достижения того, что мы делаем в Java через. extendдля расширения не абстрактных подклассов? Для меня это может быть удобным способом переопределить только определенные методы при использовании существующих, которые реализованы внутренним Interface.
Кевин Габуси
Так это что-то вроде наследования? И return r.Interface.Less(j, i)вызывает ли родительскую реализацию?
warvariuc
39

Хорошо, принятый ответ помог мне понять, но я решил опубликовать объяснение, которое, на мой взгляд, лучше соответствует моему образу мышления.

В «Effective Go» есть примеры интерфейсов, в которые встроены другие интерфейсы:

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

и структура со встроенными другими структурами:

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

Но нет упоминания о структуре со встроенным интерфейсом. Я был смущен, увидев это в sortпакете:

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

...

type reverse struct {
    Interface
}

Но идея проста. Это почти то же самое, что:

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

методы IntSliceпродвижения reverse.

И это:

type reverse struct {
    Interface
}

означает, что sort.reverseможно встроить любую структуру, реализующую интерфейс, sort.Interfaceи какие бы методы у этого интерфейса ни были, они будут продвигаться reverse.

sort.Interfaceимеет метод, Less(i, j int) boolкоторый теперь можно переопределить:

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

Мое замешательство в понимании

type reverse struct {
    Interface
}

Я думал, что структура всегда имеет фиксированную структуру, т.е. фиксированное количество полей фиксированных типов.

Но следующее доказывает мою неправоту:

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"})
}
Варвариук
источник
3
Если я правильно понимаю, значения интерфейса представлены указателем на присвоенный ему экземпляр и указателем на таблицу методов типа экземпляра. Таким образом, все значения интерфейса имеют одинаковую структуру в памяти. Конструктивно встраивание такое же, как и композиция. Таким образом, даже структура, встраивающая интерфейс, будет иметь фиксированную структуру. Структура экземпляров, на которые указывает интерфейс, будет отличаться.
Nishant George Agrwal
Я нашел этот ответ лучше, чем принятый, поскольку он дал гораздо больше подробностей, ясный пример и ссылку на документацию.
110100100
25

Заявление

type reverse struct {
    Interface
}

позволяет инициализировать reverseвсе, что реализует интерфейс Interface. Пример:

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

Таким образом, все методы, реализованные встроенным Interfaceзначением, заполняются извне, в то время как вы все еще можете переопределить некоторые из них reverse, например, Lessчтобы отменить сортировку.

Вот что на самом деле происходит, когда вы используете sort.Reverse. Вы можете прочитать о встраивании в структурном разделе спецификации .

Немо
источник
5

Я тоже дам свое объяснение. sortПакет определяет неэкспортируемый тип reverse, который является структурой, которая встраивает Interface.

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

Это позволяет Reverse использовать методы другой реализации интерфейса. Это так называемая compositionмощная особенность Go.

LessМетод reverseвызовов Lessметода вложенной 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)
}

Lenи Swapдва других метода reverseнеявно предоставляются исходным Interfaceзначением, потому что это встроенное поле. Экспортированная Reverseфункция возвращает экземпляр reverseтипа, который содержит исходное Interfaceзначение.

// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
    return &reverse{data}
}
Эндре Симо
источник
Для меня это похоже на наследование. « LessМетод for reverseвызывает Lessметод встроенного Interfaceзначения, но с перевернутыми индексами, изменяя порядок результатов сортировки на обратный». - это похоже на вызов родительской реализации.
warvariuc
Пока тип reverse имеет только одно поле, реализующее интерфейс интерфейса, он также становится членом интерфейса интерфейса: 0
Аллан Гуватудде,
1

Я нахожу эту функцию очень полезной при написании моков в тестах .

Вот такой пример:

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

Используя:

type storeMock struct {
    Store
    ...
}

Не нужно высмеивать все Storeметоды. Только HealthCheckиздеваться можно, так как в TestIsHealthyтесте используется только этот метод .

Ниже результат testкоманды:

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

Реальный пример этого примера использования можно найти при тестировании AWS SDK .


Чтобы сделать это еще более очевидным, вот уродливая альтернатива - минимум, который необходимо реализовать для удовлетворения Storeинтерфейса:

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
источник