Meaning of a struct with embedded anonymous interface?

后端 未结 5 1607
感动是毒
感动是毒 2020-12-07 08:42

sort package:

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

...

type reverse struct {
    Interface
}
         


        
相关标签:
5条回答
  • 2020-12-07 08:53

    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}
    }
    
    0 讨论(0)
  • 2020-12-07 08:57

    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"})
    }
    
    0 讨论(0)
  • 2020-12-07 08:57

    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
    }
    
    0 讨论(0)
  • 2020-12-07 09:06

    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.

    0 讨论(0)
  • 2020-12-07 09:07

    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.

    0 讨论(0)
提交回复
热议问题