具有嵌入式匿名接口的结构的含义?


87

sort 包:

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

...

type reverse struct {
    Interface
}

Interfacestruct中的匿名接口是什么意思reverse


对于搜索者而言,这里有一个简单得多的解释:从建筑师的角度近距离了解Golang。不要让文章的标题吓到你了。:)
7stud

10
AIUI,该文章(“近距离观察...”)实际上并未讨论将匿名接口嵌入到结构中的含义,而只是讨论了一般的接口。
阿德里安·路德温

Answers:


67

通过这种方式,反向实现了sort.Interface,我们可以覆盖特定的方法而不必定义所有其他方法

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

请注意,这里是如何交换(j,i)而不是交换的(i,j),这也是为struct声明的唯一方法,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. Reversesort.Interface吗?添加另一种方法?
  2. 创建另一个ReverseInterface?
  3. ...?

任何此类更改都需要跨数千个要使用标准反向功能的软件包的许多行代码。


2
因此,它允许您仅重新定义接口的某些方法?
大卫·天宇Wong

1
重要的部分是reverse具有类型的成员Interface。然后,该成员的方法可以在外部结构上调用或重写。
布莱恩

可以将此功能(或方法)视为通过Java实现我们所要做的一种方式。extend扩展非抽象子类?对我来说,这是一种方便的方法,可以在使用由internal实现的现有方法时仅覆盖某些方法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
如果我的理解是正确的,则接口值由指向分配给它的实例的指针和指向实例类型的方法表的指针表示。因此,所有接口值在内存中都具有相同的结构。在结构上,嵌入与合成相同。因此,即使嵌入接口的结构也将具有固定的结构。接口指向的实例的结构将有所不同。
Nishant George Agrwal,2015年

我发现这个答案比被接受的答案更好,因为它给出了更多的细节,清晰的示例和文档链接。
110100100

25

该声明

type reverse struct {
    Interface
}

使您可以reverse使用实现接口的所有内容进行初始化Interface。例:

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

这样,由嵌入Interface值实现的所有方法都将填充到外部,而您仍然能够在中重写其中的某些方法reverse,例如Less,使排序反向。

这就是您使用时实际发生的情况sort.Reverse。您可以在规范的struct部分中阅读有关嵌入的信息


5

我也会给出解释。所述sort包定义的未导出类型reverse,这是一个结构,嵌入Interface

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

这允许反向使用其他接口实现的方法。这就是所谓的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)
}

LenSwap的其他两种方法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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.