【问题标题】:Meaning of a struct with embedded anonymous interface?具有嵌入式匿名接口的结构的含义?
【发布时间】:2014-07-02 17:30:29
【问题描述】:

sort 包:

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

...

type reverse struct {
    Interface
}

struct reverse中匿名接口Interface是什么意思?

【问题讨论】:

  • 对于搜索者,这里有一个更简单的解释:A Closer Look at Golang From an Architect’s Perspective。不要让文章的标题吓跑你。 :)
  • AIUI,那篇文章(“A Closer Look...”)实际上并没有讨论将匿名接口嵌入到结构中意味着什么,它只是讨论了一般的接口。
  • 在结构中嵌入接口允许部分“覆盖”嵌入接口的方法。有关详细信息和排序以外的示例,请参阅my answer

标签: go


【解决方案1】:

通过这种方式反向实现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. sort.Interface 中添加另一个Reverse 方法?
  2. 创建另一个 ReverseInterface ?
  3. ... ?

任何这种更改都需要跨数千个想要使用标准反向功能的包的更多代码行。

【讨论】:

  • 所以它只允许你重新定义接口的一些方法?
  • 重要的是reverse 有一个Interface 类型的成员。然后,该成员的方法可在外部结构上调用,或可覆盖。
  • 能否将此功能(或方法)视为通过 Java 实现我们所做工作的一种方式。 extend 用于扩展非抽象子类?对我来说,这可能是一种在使用由内部Interface 实现的现有方法时仅覆盖某些方法的便捷方法。
  • 所以是一种继承?而return r.Interface.Less(j, i) 正在调用父实现?
  • 我已经是第二次对此感到困惑了。所有答案似乎都忘记了这个结构的不直观用法(实际排序任何东西都需要它):sort.Sort(sort.Reverse(sort.IntSlice(example)))。对我来说,这里的痛点是:方法 Sort 被提升为反向结构,但调用是非成员(接收者)样式。
【解决方案2】:

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

"Effective 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】:

在结构中嵌入接口允许部分“覆盖”嵌入接口的方法。这反过来又允许从嵌入结构委托到嵌入接口实现。

以下示例取自this blog post

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

type StatsConn struct {
  net.Conn

  BytesRead uint64
}

StatsConn 现在实现了net.Conn 接口,可以在任何需要net.Conn 的地方使用。当StatsConn 使用为嵌入字段实现net.Conn 的适当值初始化时,它“继承”该值的所有方法;然而,关键的见解是,我们可以拦截我们希望的任何方法,而保留所有其他方法。在本例中,我们希望拦截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 接口有很多方法。为所有这些编写转发方法是乏味且不必要的。嵌入接口为我们免费提供了所有这些转发方法,我们可以只覆盖我们需要的那些。

【讨论】:

    【解决方案4】:

    声明

    type reverse struct {
        Interface
    }
    

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

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

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

    这就是您使用sort.Reverse 时实际发生的情况。您可以阅读有关嵌入 in the struct section of the spec 的信息。

    【讨论】:

    • 我已经是第二次对此感到困惑了。所有答案似乎都忘记了这个结构的不直观用法(实际排序任何东西都需要):sort.Sort(sort.Reverse(sort.IntSlice(example)))。对我来说,这里的痛点是:方法 Sort 被提升为反向结构,但调用是非成员(接收者)样式。
    • @seebi 如果这是一个问题,我不明白,抱歉。同样Sort方法没有被提升,它需要满足sort.Interface的东西,reverse就是这样的东西,它只是改变了它嵌入的sort.Interface数据的相关方法,以便得到的排序被反转。
    • 不是问题,只是评论,你说得对,我指的是 Less、Swap、Len 方法!
    【解决方案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")
        }
    }
    

    通过使用:

    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
    }
    

    【讨论】:

      【解决方案6】:

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

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

      这允许 Reverse 使用另一个接口实现的方法。这就是所谓的composition,这是 Go 的一个强大功能。

      reverseLess 方法调用嵌入的 Less 值的 Less 方法,但索引会翻转,从而颠倒排序结果的顺序。

      // 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 函数返回包含原始Interface 值的reverse 类型的实例。

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

      【讨论】:

      • 对我来说这看起来像继承。 “reverseLess 方法调用了嵌入的 Interface 值的 Less 方法,但索引翻转,反转排序结果的顺序。” -- 这看起来像调用父实现。
      • 只要类型 reverse 只有一个实现 Interface 接口的字段,它也成为 Interface 接口的成员:0
      【解决方案7】:

      我将尝试另一种低级别的方法。 给定反向结构:

      type reverse struct {
          Interface
      }
      

      这意味着,反向结构有一个字段reverse.Interface,作为结构字段,它可以为nil或具有接口类型的值。 如果它不是 nil,那么接口中的字段将被提升为“root”=reverse struct。它可能会被直接定义在反向结构上的字段所掩盖,但这不是我们的情况。

      当您执行以下操作时: foo := reverse{},你可以通过 fmt.Printf("%+v", foo) println 得到

      {Interface:<nil>}
      

      当你这样做时

      foo := reverse{someInterfaceInstance}
      

      相当于:

      foo := reverse{Interface: someInterfaceInstance}
      

      感觉就像你声明了期望,Interface API 的实现应该在运行时注入到你的结构 reverse 中。然后这个api会被提升到struct reverse的根。 同时,这仍然允许不一致,你有反向结构实例与 reverse.Interface = ,你编译它并在运行时得到恐慌。

      当我们回顾 OP 中反向的具体示例时,我可以将其视为一种模式,即如何在运行时替换/扩展某些实例/实现类型的行为,而不是像在编译中那样使用类型嵌入结构而不是接口的时间。

      不过,这让我很困惑。尤其是接口为 Nil 的状态 :(.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-02-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-19
        • 1970-01-01
        • 2018-01-27
        • 2015-05-16
        • 1970-01-01
        相关资源
        最近更新 更多