Ý nghĩa của một cấu trúc có nhúng giao diện ẩn danh?


87

sort gói:

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

...

type reverse struct {
    Interface
}

Ý nghĩa của giao diện ẩn danh Interfacetrong struct là reversegì?


Đối với những người tìm kiếm, có một lời giải thích đơn giản hơn nhiều ở đây: Cái nhìn cận cảnh hơn về Golang từ góc độ kiến ​​trúc sư . Đừng để tiêu đề của bài báo làm bạn sợ hãi. :)
7stud

10
AIUI, bài viết đó ("A Closer Look ...") không thực sự nói về ý nghĩa của việc nhúng các giao diện ẩn danh vào một cấu trúc, nó chỉ nói về các giao diện nói chung.
Adrian Ludwin

Câu trả lời:


68

Bằng cách này, đảo ngược thực hiện sort.Interfacevà chúng ta có thể ghi đè một phương thức cụ thể mà không cần phải xác định tất cả các phương thức khác

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

Lưu ý cách ở đây nó hoán đổi (j,i)thay vì (i,j)và đây cũng là phương thức duy nhất được khai báo cho cấu trúc reversengay cả khi reversetriển khaisort.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)
}

Bất kỳ cấu trúc nào được chuyển vào bên trong phương thức này, chúng tôi chuyển đổi nó thành một reversecấu trúc mới .

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

Giá trị thực sẽ đến nếu bạn nghĩ rằng bạn sẽ phải làm gì nếu cách tiếp cận này không thể thực hiện được.

  1. Thêm một Reversephương thức khác vào sort.Interface?
  2. Tạo một ReverseInterface khác?
  3. ...?

Bất kỳ thay đổi nào trong số này sẽ yêu cầu nhiều dòng mã hơn trên hàng nghìn gói muốn sử dụng chức năng đảo ngược tiêu chuẩn.


2
vì vậy nó cho phép bạn xác định lại một số phương thức của một giao diện?
David 天宇 Wong

1
Phần quan trọng là reversecó một thành viên của loại Interface. Thành viên này sau đó có thể gọi các phương thức của nó trên cấu trúc bên ngoài hoặc có thể ghi đè.
Bryan

Tính năng (hoặc cách tiếp cận) này có thể được coi là một cách để đạt được những gì chúng ta làm trong Java thông qua. extendđể mở rộng các lớp con không trừu tượng? Đối với tôi, đây có thể là một cách tiện dụng để ghi đè chỉ một số phương pháp nhất định trong khi sử dụng các phương thức hiện có được thực hiện bởi nội bộ Interface.
Kevin Ghaboosi 19/02/18

Vậy đó là một kiểu thừa kế? Và return r.Interface.Less(j, i)đang gọi thực hiện cha mẹ?
warvariuc

39

Ok, câu trả lời được chấp nhận đã giúp tôi hiểu, nhưng tôi quyết định đăng một lời giải thích mà tôi nghĩ phù hợp hơn với cách suy nghĩ của tôi.

Các "hiệu quả Go" có ví dụ về các giao diện có nhúng giao diện khác:

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

và một cấu trúc có nhúng các cấu trúc khác:

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

Nhưng không có đề cập đến một cấu trúc có nhúng một giao diện. Tôi đã bối rối khi thấy điều này trong sortgói:

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

...

type reverse struct {
    Interface
}

Nhưng ý tưởng rất đơn giản. Nó gần giống như:

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

phương pháp IntSliceđược thăng chức reverse.

Và điều này:

type reverse struct {
    Interface
}

có nghĩa là sort.reversecó thể nhúng bất kỳ cấu trúc nào triển khai giao diện sort.Interfacevà bất kỳ phương thức nào mà giao diện đó có, chúng sẽ được thăng cấp reverse.

sort.Interfacecó phương thức Less(i, j int) boolmà bây giờ có thể được ghi đè:

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

Sự bối rối của tôi trong sự hiểu biết

type reverse struct {
    Interface
}

là tôi đã nghĩ rằng một cấu trúc luôn có cấu trúc cố định, tức là một số trường cố định thuộc loại cố định.

Nhưng những điều sau đây chứng minh tôi đã sai:

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
Nếu sự hiểu biết của tôi là đúng, các giá trị giao diện được biểu diễn bằng con trỏ aa tới thể hiện được gán cho nó và một con trỏ tới bảng phương thức của loại thể hiện. Vì vậy, tất cả các giá trị giao diện có cấu trúc giống nhau trong bộ nhớ. Về mặt cấu trúc, nhúng cũng giống như thành phần. Vì vậy, ngay cả một cấu trúc nhúng một giao diện sẽ có một cấu trúc cố định. Cấu trúc của các cá thể mà giao diện trỏ tới sẽ khác nhau.
Nishant George Agrwal

Tôi thấy đây là một câu trả lời tốt hơn câu được chấp nhận vì nó cung cấp nhiều chi tiết hơn, một ví dụ rõ ràng và một liên kết đến tài liệu.
110100100

25

Tuyên bố

type reverse struct {
    Interface
}

cho phép bạn khởi tạo reversevới mọi thứ triển khai giao diện Interface. Thí dụ:

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

Bằng cách này, tất cả các phương pháp được thực hiện bởi Interfacegiá trị nhúng sẽ được phổ biến ra bên ngoài trong khi bạn vẫn có thể ghi đè một số phương pháp trong đó reverse, chẳng hạn như Lessđể đảo ngược việc sắp xếp.

Đây là những gì thực sự xảy ra khi bạn sử dụng sort.Reverse. Bạn có thể đọc về cách nhúng trong phần cấu trúc của thông số kỹ thuật .


6

Tôi cũng sẽ đưa ra lời giải thích của tôi. Các sortgói định nghĩa một kiểu unexported reverse, mà là một cấu trúc, mà nhúng Interface.

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

Điều này cho phép Reverse sử dụng các phương pháp triển khai Giao diện khác. Đây là cái gọi là composition, là một tính năng mạnh mẽ của cờ vây.

Các Lessphương pháp cho reversecác cuộc gọi các Lessphương pháp nhúng Interfacegiá trị, nhưng với các chỉ số lộn, đảo ngược thứ tự của các kết quả phân loại.

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

LenSwaphai phương thức khác của reverse, được cung cấp ngầm bởi Interfacegiá trị ban đầu vì nó là một trường nhúng. Hàm đã xuất Reversetrả về một thể hiện của reversekiểu có chứa Interfacegiá trị ban đầu .

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

Đối với tôi, điều này giống như thừa kế. " LessPhương thức cho reversegọi Lessphương thức của Interfacegiá trị nhúng , nhưng với các chỉ số bị lật, đảo ngược thứ tự của kết quả sắp xếp." - điều này trông giống như việc gọi triển khai cha mẹ.
warvariuc

Miễn là kiểu đảo ngược chỉ có một trường triển khai giao diện Giao diện, nó cũng trở thành thành viên của giao diện Giao diện: 0
Allan Guwatudde

1

Tôi thấy tính năng này rất hữu ích khi viết mocks trong các bài kiểm tra .

Đây là một ví dụ:

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

Bằng cách sử dụng:

type storeMock struct {
    Store
    ...
}

Người ta không cần phải chế nhạo tất cả các Storephương pháp. Chỉ HealthCheckcó thể bị chế nhạo, vì chỉ có phương pháp này được sử dụng trong TestIsHealthythử nghiệm.

Dưới kết quả của testlệnh:

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

Một ví dụ thực tế về trường hợp sử dụng này mà người ta có thể tìm thấy khi thử nghiệm AWS SDK .


Để làm cho nó rõ ràng hơn, đây là phương án thay thế xấu xí - điều tối thiểu cần phải triển khai để đáp ứng Storegiao diện:

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
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.