Значення структури із вбудованим анонімним інтерфейсом?


87

sort пакет:

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

...

type reverse struct {
    Interface
}

Що означає анонімний інтерфейс Interfaceу struct reverse?


Для пошукачів тут є набагато простіше пояснення: Пильніший погляд на Голанг з точки зору архітектора . Не дозволяйте назві статті вас лякати. :)
— 7студ

10
AIUI, ця стаття ("Пильніший погляд ...") насправді не говорить про те, що означає вбудовувати анонімні інтерфейси у структуру, вона просто говорить про інтерфейси загалом.
— Адріан Людвін

Відповіді:


67

Таким чином реверсна реалізація 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

Гаразд, прийнята відповідь допомогла мені зрозуміти, але я вирішив опублікувати пояснення, яке, на мою думку, більше підходить для мого способу мислення.

«Ефективне 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
Якщо я розумію правильно, значення інтерфейсу представлені вказівником на присвоєний йому екземпляр та вказівником на таблицю методів типу екземпляра. Отже, всі значення інтерфейсу мають однакову структуру в пам'яті. Структурно вкладання - це те саме, що композиція. Отже, навіть структура, що вбудовує інтерфейс, матиме фіксовану структуру. Структури екземплярів, на які вказує інтерфейс, будуть різними.
— Нішант Джордж Агрвал

Я знайшов, що це краща відповідь, ніж прийнята, оскільки вона дає набагато більше деталей, наочний приклад та посилання на документацію.
— 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Метод для reverseвикликів Lessметоду вбудованого Interfaceзначення, але з індексами, перевернутими, змінюючи порядок результатів сортування." - це виглядає як виклик батьківської реалізації.
— warvariuc

Поки тип зворотного має лише одне поле, яке реалізує інтерфейс інтерфейсу, він також стає членом інтерфейсу інтерфейсу: 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
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.