英文:
Meaning of a struct with embedded anonymous interface?
问题
在reverse
结构体中,匿名接口Interface
的含义是什么?
匿名接口Interface
在reverse
结构体中的作用是嵌入了一个接口类型,通过嵌入接口类型,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}
}
如果考虑一下,如果这种方法不可行,你需要做什么。
- 在
sort.Interface
中添加另一个Reverse
方法吗? - 创建另一个
ReverseInterface
接口? - ...?
任何这种更改都需要在成千上万个想要使用标准反向功能的包中编写更多的代码。
英文:
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.
- Add another
Reverse
method to thesort.Interface
? - Create another ReverseInterface ?
- ... ?
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 语言的一个强大特性。
reverse
的 Less
方法调用了嵌入的 Interface
值的 Less
方法,但是索引位置颠倒了,从而颠倒了排序结果的顺序。
// Less 返回嵌入实现的 Less 方法的相反结果。
func (r reverse) Less(i, j int) bool {
return r.Interface.Less(j, i)
}
Len
和 Swap
是 reverse
的另外两个方法,它们由于是嵌入字段,所以被原始的 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:<nil>}
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 :(.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论