嵌入匿名接口的结构体的含义是什么?

huangapple go评论92阅读模式
英文:

Meaning of a struct with embedded anonymous interface?

问题

reverse结构体中,匿名接口Interface的含义是什么?

匿名接口Interfacereverse结构体中的作用是嵌入了一个接口类型,通过嵌入接口类型,reverse结构体可以继承接口的方法集合。这意味着reverse结构体可以直接访问和调用Interface接口中定义的方法,而无需通过接口变量来调用。通过嵌入接口类型,reverse结构体可以实现接口的所有方法,从而满足接口的要求。

英文:

sort package:

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

...

type reverse struct {
	Interface
}

What is the meaning of anonymous interface Interface in struct reverse?

答案1

得分: 93

通过这种方式,我们可以实现sort.Interface的反向排序,并且可以重写特定的方法,而无需定义其他所有方法。

type reverse struct {
    // 这个嵌入的 Interface 允许 Reverse 使用另一个 Interface 实现的方法。
    Interface
}

// 注意这里交换了 `(j,i)` 而不是 `(i,j)`,并且这是结构体 `reverse` 唯一声明的方法,即使 `reverse` 实现了 `sort.Interface`。
func (r reverse) Less(i, j int) bool {
    return r.Interface.Less(j, i)
}

// 无论传入的是哪个结构体,我们都将其转换为新的 `reverse` 结构体。
func Reverse(data Interface) Interface {
    return &reverse{data}
}

如果考虑一下,如果这种方法不可行,你需要做什么。

  1. sort.Interface 中添加另一个 Reverse 方法吗?
  2. 创建另一个 ReverseInterface 接口?
  3. ...?

任何这种更改都需要在成千上万个想要使用标准反向功能的包中编写更多的代码。

英文:

In this way reverse implements the sort.Interface and we can override a specific method
without having to define all the others

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

Notice how here it swaps (j,i) instead of (i,j) and also this is the only method declared for the struct reverse even if reverse implement 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)
}

Whatever struct is passed inside this method we convert it to a new reverse struct.

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

The real value comes if you think what would you have to do if this approach was not possible.

  1. Add another Reverse method to the sort.Interface ?
  2. Create another ReverseInterface ?
  3. ... ?

Any of this change would require many many more lines of code across thousands of packages that want to use the standard reverse functionality.

答案2

得分: 52

好的,以下是翻译好的内容:

好的,接受的答案帮助我理解了,但我决定发表一个解释,我认为更适合我的思维方式。

《Effective Go》中有接口嵌入其他接口的示例:

// ReadWriter 是将 Reader 和 Writer 接口组合在一起的接口。
type ReadWriter interface {
    Reader
    Writer
}

还有一个结构体嵌入其他结构体的示例:

// ReadWriter 存储指向 Reader 和 Writer 的指针。
// 它实现了 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 结构体将 Interface 的方法附加到 []int,以升序排序
}

IntSlice 的方法被提升到 reverse 中。

而这个:

type reverse struct {
    Interface
}

意味着 sort.reverse 可以嵌入任何实现了 sort.Interface 接口的结构体,无论该接口有什么方法,它们都将被提升到 reverse 中。

sort.Interface 有一个方法 Less(i, j int) bool,现在可以被重写:

// Less 返回嵌入实现的 Less 方法的相反结果。
func (r reverse) Less(i, j int) bool {
    return r.Interface.Less(j, i)
}

我对理解以下代码感到困惑:

type reverse struct {
    Interface
}

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

但是下面的代码证明了我是错误的:

package main

import "fmt"

// 一些接口
type Stringer interface {
    String() string
}

// 实现了 Stringer 接口的结构体
type Struct1 struct {
    field1 string
}

func (s Struct1) String() string {
    return s.field1
}

// 另一个实现了 Stringer 接口的结构体,但具有不同的字段集合
type Struct2 struct {
    field1 []string
    dummy bool
}

func (s Struct2) String() string {
    return fmt.Sprintf("%v, %v", s.field1, s.dummy)
}

// 可以嵌入任何实现了 Stringer 接口的结构体的容器
type StringerContainer struct {
    Stringer
}

func main() {
    // 输出:This is Struct1
    fmt.Println(StringerContainer{Struct1{"This is Struct1"}})
    // 输出:[This is Struct1], true
    fmt.Println(StringerContainer{Struct2{[]string{"This", "is", "Struct1"}, true}})
    // 以下代码无法编译通过:
    // 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"})
}
英文:

Ok, the accepted answer helped me understand, but I decided to post an explanation which I think suits better my way of thinking.

The "Effective Go" has example of interfaces having embedded other interfaces:

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

and a struct having embedded other structs:

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

But there is no mention of a struct having embedded an interface. I was confused seeing this in sort package:

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

...

type reverse struct {
	Interface
}

But the idea is simple. It's almost the same as:

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

methods of IntSlice being promoted to reverse.

And this:

type reverse struct {
    Interface
}

means that sort.reverse can embed any struct that implements interface sort.Interface and whatever methods that interface has, they will be promoted to reverse.

sort.Interface has method Less(i, j int) bool which now can be overridden:

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

My confusion in understanding

type reverse struct {
    Interface
}

was that I thought that a struct always has fixed structure, i.e. fixed number of fields of fixed types.

But the following proves me wrong:

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

得分: 30

声明

type reverse struct {
    Interface
}

使您能够使用实现接口Interface的所有内容来初始化reverse。例如:

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

这样,嵌入的Interface值实现的所有方法都会在外部填充,同时您仍然可以在reverse中覆盖其中的一些方法,例如Less以反转排序。

这实际上是在使用sort.Reverse时发生的情况。您可以在规范的结构部分中了解有关嵌入的更多信息。

英文:

The statement

type reverse struct {
    Interface
}

enables you to initialize reverse with everything that implements the interface Interface. Example:

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

This way, all methods implemented by the embedded Interface value get populated to the outside while you are still able to override some of them in reverse, for example Less to reverse the sorting.

This is what actually happens when you use sort.Reverse. You can read about embedding in the struct section of the spec.

答案4

得分: 8

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

type reverse struct {
    // 这个嵌入的 Interface 允许 Reverse 使用另一个 Interface 实现的方法。
    Interface
}

这使得 Reverse 可以使用另一个 Interface 实现的方法。这就是所谓的 组合,它是 Go 语言的一个强大特性。

reverseLess 方法调用了嵌入的 Interface 值的 Less 方法,但是索引位置颠倒了,从而颠倒了排序结果的顺序。

// Less 返回嵌入实现的 Less 方法的相反结果。
func (r reverse) Less(i, j int) bool {
    return r.Interface.Less(j, i)
}

LenSwapreverse 的另外两个方法,它们由于是嵌入字段,所以被原始的 Interface 值隐式提供。导出的 Reverse 函数返回一个包含原始 Interface 值的 reverse 类型的实例。

// Reverse 返回数据的逆序。
func Reverse(data Interface) Interface {
    return &reverse{data}
}
英文:

I will give my explanation too. The sort package defines an unexported type reverse, which is a struct, that embeds Interface.

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

This permits Reverse to use the methods of another Interface implementation. This is the so called composition, which is a powerful feature of Go.

The Less method for reverse calls the Less method of the embedded Interface value, but with the indices flipped, reversing the order of the sort results.

// 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 and Swap the other two methods of reverse, are implicitly provided by the original Interface value because it is an embedded field. The exported Reverse function returns an instance of the reverse type that contains the original Interface value.

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

答案5

得分: 4

我发现在编写测试时,这个功能非常有帮助,特别是在编写模拟对象时。

以下是一个示例:

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
}
英文:

I find this feature very helpful when writing mocks in tests.

Here is such an example:

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

By using:

type storeMock struct {
    Store
    ...
}

One doesn't need to mock all Store methods. Only HealthCheck can be mocked, since only this method is used in the TestIsHealthy test.

Below the result of the test command:

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

A real world example of this use case one can find when testing the AWS SDK.


To make it even more obvious, here is the ugly alternative - the minimum one needs to implement to satisfy the Store interface:

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
}

答案6

得分: 3

在结构体中嵌入接口允许部分地“覆盖”嵌入接口的方法。这样,就可以从嵌入结构体委托给嵌入接口的实现。

以下示例摘自这篇博文

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

type StatsConn struct {
  net.Conn

  BytesRead uint64
}

StatsConn现在实现了net.Conn接口,并且可以在任何需要net.Conn的地方使用。当使用一个实现了net.Conn接口的合适值来初始化StatsConn时,它会“继承”该值的所有方法;关键的洞察力在于,我们可以拦截任何我们希望的方法,同时保留其他所有方法。在这个示例中,我们希望拦截Read方法并记录读取的字节数:

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

对于StatsConn的用户来说,这个改变是透明的;我们仍然可以在其上调用Read方法,它会按照我们的期望工作(因为它委托给了sc.Conn.Read),但它还会进行额外的记录。

正确初始化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}

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

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

resp, err := ioutil.ReadAll(sconn)
if err != nil {
  log.Fatal(err)
}

然后,我们可以访问它的BytesRead字段以获取总字节数。

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

type StatsConn struct {
  conn net.Conn

  BytesRead uint64
}

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

func (sc *StatsConn) Close() error {
  return sc.conn.Close()
}

然而,net.Conn接口有很多方法。为所有这些方法编写转发方法是繁琐且不必要的。通过嵌入接口,我们可以免费获得所有这些转发方法,并且只需要覆盖我们需要的方法。

英文:

Embedding interfaces in a struct allows for partially "overriding" methods from the embedded interfaces. This, in turn, allows for delegation from the embedding struct to the embedded interface implementation.

The following example is taken from this blog post.

Suppose we want to have a socket connection with some additional functionality, like counting the total number of bytes read from it. We can define the following struct:

type StatsConn struct {
  net.Conn

  BytesRead uint64
}

StatsConn now implements the net.Conn interface and can be used anywhere a net.Conn is expected. When a StatsConn is initialized with a proper value implementing net.Conn for the embedded field, it "inherits" all the methods of that value; the key insight is, though, that we can intercept any method we wish, leaving all the others intact. For our purpose in this example, we'd like to intercept the Read method and record the number of bytes read:

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

To users of StatsConn, this change is transparent; we can still call Read on it and it will do what we expect (due to delegating to sc.Conn.Read), but it will also do additional bookkeeping.

It's critical to initialize a StatsConn properly, otherwise the field retains its default value nil causing a runtime error: invalid memory address or nil pointer dereference; for example:

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

Here net.Dial returns a value that implements net.Conn, so we can use that to initialize the embedded field of StatsConn.

We can now pass our sconn to any function that expects a net.Conn argument, e.g:

resp, err := ioutil.ReadAll(sconn)
if err != nil {
  log.Fatal(err)

And later we can access its BytesRead field to get the total.

This is an example of wrapping an interface. We created a new type that implements an existing interface, but reused an embedded value to implement most of the functionality. We could implement this without embedding by having an explicit conn field like this:

type StatsConn struct {
  conn net.Conn

  BytesRead uint64
}

And then writing forwarding methods for each method in the net.Conn interface, e.g.:

func (sc *StatsConn) Close() error {
  return sc.conn.Close()
}

However, the net.Conn interface has many methods. Writing forwarding methods for all of them is tedious and unnecessary. Embedding the interface gives us all these forwarding methods for free, and we can override just the ones we need.

答案7

得分: 0

我将尝试另一种低级方法来解决这个问题。
给定反向结构体:

type reverse struct {
    Interface
}

这意味着反向结构体有一个字段reverse.Interface,作为结构体字段,它可以是nil,也可以是Interface类型的值。
如果它不是nil,那么Interface中的字段将被提升到"root" = 反向结构体。它可能会被直接在反向结构体上定义的字段所遮蔽,但这不是我们的情况。

当你这样做时:
foo := reverse{},你可以通过fmt.Printf("%+v", foo)打印它,得到

{Interface:<nil>}

当你这样做时:

foo := reverse{someInterfaceInstance}

它等同于:

foo := reverse{Interface: someInterfaceInstance}

对我来说,这感觉像是你声明了一个期望,即在运行时将Interface API的实现注入到你的结构体reverse中。然后这个API将被提升到结构体reverse的根部。
与此同时,这仍然允许不一致的情况,即你拥有一个reverse结构体实例,其中reverse.Interface = ,你编译它并在运行时出现恐慌。

当我们回顾一下OP中的反向结构体的具体示例时,我可以将其视为一种模式,通过它你可以在运行时替换/扩展某个实例/实现的行为,与更多地使用类型进行嵌入而不是接口的编译时工作相反。

但是,这仍然让我感到困惑。特别是Interface为Nil的状态:(。

英文:

I will try another, low level approach to this.
Given the reverse struct:

type reverse struct {
    Interface
}

This beside others means, that reverse struct has a field reverse.Interface, and as a struct fields, it can be nil or have value of type Interface.
If it is not nil, then the fields from the Interface are promoted to the "root" = reverse struct. It might be eclipsed by fields defined directly on the reverse struct, but that is not our case.

When You do something like:
foo := reverse{}, you can println it via fmt.Printf("%+v", foo) and got

{Interface:&lt;nil&gt;}

When you do the

foo := reverse{someInterfaceInstance}

It is equivalent of:

foo := reverse{Interface: someInterfaceInstance}

It feels to me like You declare expectation, that implementation of Interface API should by injected into your struct reverse in runtime. And this api will be then promoted to the root of struct reverse.
At the same time, this still allow inconsistency, where You have reverse struct instance with reverse.Interface = < Nil>, You compile it and get the panic on runtime.

When we look back to the specifically example of the reverse in OP, I can see it as a pattern, how you can replace/extend behaviour of some instance / implementation kind of in runtime contrary to working with types more like in compile time when You do embedding of structs instead of interfaces.

Still, it confuses me a lot. Especially the state where the Interface is Nil :(.

huangapple
  • 本文由 发表于 2014年7月3日 01:30:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/24537443.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定