sort пакет:
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
...
type reverse struct {
Interface
}
Що означає анонімний інтерфейс Interfaceу struct reverse?
sort пакет:
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
...
type reverse struct {
Interface
}
Що означає анонімний інтерфейс Interfaceу struct 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) чи викликає батьківська реалізація?
Гаразд, прийнята відповідь допомогла мені зрозуміти, але я вирішив опублікувати пояснення, яке, на мою думку, більше підходить для мого способу мислення.
«Ефективне 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Метод для 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
}