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

war*_*iuc 76 go

sort 包:

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

...

type reverse struct {
    Interface
}
Run Code Online (Sandbox Code Playgroud)

Interfacestruct 中匿名接口的含义是什么reverse

fab*_*ioM 61

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

type reverse struct {
        // This embedded Interface permits Reverse to use the methods of
        // another Interface implementation.
        Interface
}
Run Code Online (Sandbox Code Playgroud)

注意这里它是如何交换的(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)
}
Run Code Online (Sandbox Code Playgroud)

无论在此方法中传递什么结构,我们都将其转换为新reverse结构.

// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
        return &reverse{data}
}
Run Code Online (Sandbox Code Playgroud)

如果你认为如果不可能采取这种方法你需要做什么,那么真正的价值就来了.

  1. 添加另一种Reverse方法sort.Interface
  2. 创建另一个ReverseInterface?
  3. ......?

任何这种改变都需要在数千个想要使用标准反向功能的软件包上提供更多代码.

  • 所以它允许你重新定义一个接口的一些方法? (2认同)
  • 重要的部分是 `reverse` 有一个类型为 `Interface` 的 _member_。然后,该成员的方法可在外部结构上调用,或可覆盖。 (2认同)

war*_*iuc 33

好的,接受的答案帮助我理解,但我决定发布一个我觉得更适合我的思维方式的解释.

所述"有效Go"的具有示例的具有嵌入其它接口接口:

// ReadWriter is the interface that combines the Reader and Writer interfaces.
type ReadWriter interface {
    Reader
    Writer
}
Run Code Online (Sandbox Code Playgroud)

和嵌入其他结构的结构:

// ReadWriter stores pointers to a Reader and a Writer.
// It implements io.ReadWriter.
type ReadWriter struct {
    *Reader  // *bufio.Reader
    *Writer  // *bufio.Writer
}
Run Code Online (Sandbox Code Playgroud)

但是没有提到嵌入了接口的结构.在sort包中看到这个我很困惑:

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

...

type reverse struct {
    Interface
}
Run Code Online (Sandbox Code Playgroud)

但这个想法很简单.它几乎相同:

type reverse struct {
    IntSlice  // IntSlice struct attaches the methods of Interface to []int, sorting in increasing order
}
Run Code Online (Sandbox Code Playgroud)

IntSlice被提升为的方法reverse.

还有这个:

type reverse struct {
    Interface
}
Run Code Online (Sandbox Code Playgroud)

意味着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)
}
Run Code Online (Sandbox Code Playgroud)

我对理解的困惑

type reverse struct {
    Interface
}
Run Code Online (Sandbox Code Playgroud)

是因为我认为结构总是有固定的结构,即固定类型的固定数量的字段.

但以下证明我错了:

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"})
}
Run Code Online (Sandbox Code Playgroud)

  • 如果我的理解是正确的,接口值由指向分配给它的实例的指针和指向实例类型的方法表的指针表示.因此,所有接口值在内存中都具有相同的结构.在结构上,嵌入与组合相同.因此,即使嵌入接口的结构也会有固定的结构.接口所指向的实例的结构将是不同的. (3认同)

nem*_*emo 22

该声明

type reverse struct {
    Interface
}
Run Code Online (Sandbox Code Playgroud)

使您能够reverse使用实现该接口的所有内容进行初始化Interface.例:

&reverse{sort.Intslice([]int{1,2,3})}
Run Code Online (Sandbox Code Playgroud)

这样,嵌入Interface值实现的所有方法都会填充到外部,而您仍然可以覆盖它们中的一些reverse,例如Less反转排序.

这是您使用时实际发生的情况sort.Reverse.您可以阅读有关嵌入规范的struct部分的内容.


Abh*_*kar 6

在结构中嵌入接口允许部分“重写”嵌入接口中的方法。反过来,这允许从嵌入结构委托给嵌入接口实现。

以下示例取自这篇博文

假设我们想要一个带有一些附加功能的套接字连接,例如计算从中读取的字节总数。我们可以定义以下结构体:

type StatsConn struct {
  net.Conn

  BytesRead uint64
}
Run Code Online (Sandbox Code Playgroud)

StatsConn现在实现了该接口,并且可以在任何需要net.Conna 的地方使用。net.Conn当 aStatsConn使用嵌入字段实现的适当值进行初始化时net.Conn,它会“继承”该值的所有方法;不过,关键的见解是,我们可以拦截任何我们想要的方法,而让其他所有方法保持不变。出于本示例的目的,我们希望拦截该Read方法并记录读取的字节数:

func (sc *StatsConn) Read(p []byte) (int, error) {
  n, err := sc.Conn.Read(p)
  sc.BytesRead += uint64(n)
  return n, err
}
Run Code Online (Sandbox Code Playgroud)

对于 的用户来说StatsConn,这种变化是透明的;我们仍然可以调用Read它,它会做我们期望的事情(由于委托给sc.Conn.Read),但它也会做额外的簿记。

正确初始化 a 至关重要StatsConn,否则该字段将保留其默认值,nil从而导致runtime error: invalid memory address or nil pointer dereference; 例如:

conn, err := net.Dial("tcp", u.Host+":80")
if err != nil {
  log.Fatal(err)
}
sconn := &StatsConn{conn, 0}
Run Code Online (Sandbox Code Playgroud)

这里net.Dial返回一个实现 的值net.Conn,因此我们可以使用它来初始化 的嵌入字段StatsConn

我们现在可以将 our 传递sconn给任何需要net.Conn参数的函数,例如:

resp, err := ioutil.ReadAll(sconn)
if err != nil {
  log.Fatal(err)
Run Code Online (Sandbox Code Playgroud)

之后我们可以访问它的BytesRead字段来获取总数。

这是包装接口的示例。我们创建了一个实现现有接口的新类型,但重用了嵌入值来实现大部分功能。我们可以通过使用conn如下所示的显式字段来实现这一点,而无需嵌入:

type StatsConn struct {
  conn net.Conn

  BytesRead uint64
}
Run Code Online (Sandbox Code Playgroud)

然后为net.Conn接口中的每个方法编写转发方法,eg:

func (sc *StatsConn) Close() error {
  return sc.conn.Close()
}
Run Code Online (Sandbox Code Playgroud)

然而,该net.Conn接口有很多方法。为所有这些都编写转发方法是乏味且不必要的。嵌入接口免费为我们提供了所有这些转发方法,并且我们可以只覆盖我们需要的那些。


End*_*imo 5

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

type reverse struct {
    // This embedded Interface permits Reverse to use the methods of
    // another Interface implementation.
    Interface
}
Run Code Online (Sandbox Code Playgroud)

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

LenSwap的其他两种方法reverse由原始Interface值隐式提供,因为它是一个嵌入式字段。导出的Reverse函数返回一个reverse包含原始Interface值的类型的实例。

// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
    return &reverse{data}
}
Run Code Online (Sandbox Code Playgroud)


cze*_*asz 5

我发现这个功能在测试中编写模拟时非常有用。

这是这样一个例子:

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")
    }
}
Run Code Online (Sandbox Code Playgroud)

通过使用:

type storeMock struct {
    Store
    ...
}
Run Code Online (Sandbox Code Playgroud)

不需要模拟所有Store方法。仅有的HealthCheck可以被mock,因为测试中只使用了这个方法TestIsHealthy

命令结果如下test

$ go test -run '^TestIsHealthy$' ./main_test.go           
ok      command-line-arguments  0.003s
Run Code Online (Sandbox Code Playgroud)

在测试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
}
Run Code Online (Sandbox Code Playgroud)