sort пакет:
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
...
type reverse struct {
Interface
}
В чем смысл анонимного интерфейса Interfaceв структуре reverse?
sort пакет:
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
...
type reverse struct {
Interface
}
В чем смысл анонимного интерфейса Interfaceв структуре reverse?
Ответы:
Таким образом, reverse реализует, 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}
}
Настоящая ценность приходит, если вы думаете, что бы вы сделали, если бы такой подход был невозможен.
Reverseметод в sort.Interface?Любое из этих изменений потребует намного большего количества строк кода в тысячах пакетов, которые хотят использовать стандартные функции обратного преобразования.
reverseэто член типа Interface. Затем у этого члена есть методы, вызываемые во внешней структуре или переопределяемые.
extendдля расширения не абстрактных подклассов? Для меня это может быть удобным способом переопределить только определенные методы при использовании существующих, которые реализованы внутренним Interface.
return r.Interface.Less(j, i)вызывает ли родительскую реализацию?
Хорошо, принятый ответ помог мне понять, но я решил опубликовать объяснение, которое, на мой взгляд, лучше соответствует моему образу мышления.
В «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"})
}
Заявление
type reverse struct {
Interface
}
позволяет инициализировать reverseвсе, что реализует интерфейс Interface. Пример:
&reverse{sort.Intslice([]int{1,2,3})}
Таким образом, все методы, реализованные встроенным Interfaceзначением, заполняются извне, в то время как вы все еще можете переопределить некоторые из них reverse, например, Lessчтобы отменить сортировку.
Вот что на самом деле происходит, когда вы используете sort.Reverse. Вы можете прочитать о встраивании в структурном разделе спецификации .
Я тоже дам свое объяснение. sortПакет определяет неэкспортируемый тип reverse, который является структурой, которая встраивает Interface.
type reverse struct {
// This embedded Interface permits Reverse to use the methods of
// another Interface implementation.
Interface
}
Это позволяет Reverse использовать методы другой реализации интерфейса. Это так называемая compositionмощная особенность Go.
LessМетод reverseвызовов Lessметода вложенной 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)
}
Lenи Swapдва других метода reverseнеявно предоставляются исходным Interfaceзначением, потому что это встроенное поле. Экспортированная Reverseфункция возвращает экземпляр reverseтипа, который содержит исходное Interfaceзначение.
// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
return &reverse{data}
}
LessМетод for reverseвызывает Lessметод встроенного Interfaceзначения, но с перевернутыми индексами, изменяя порядок результатов сортировки на обратный». - это похоже на вызов родительской реализации.
Я нахожу эту функцию очень полезной при написании моков в тестах .
Вот такой пример:
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
}