Signification d'une structure avec interface anonyme intégrée?

87

sort paquet:

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

...

type reverse struct {
    Interface
}

Quelle est la signification de l'interface anonyme Interfacedans struct reverse?

warvariuc
la source
Pour les chercheurs, il y a une explication beaucoup plus simple ici: Un regard plus approfondi sur Golang du point de vue d'un architecte . Ne laissez pas le titre de l'article vous effrayer. :)
7stud
10
AIUI, cet article ("A Closer Look ...") ne parle pas réellement de ce que signifie intégrer des interfaces anonymes dans une structure, il parle simplement des interfaces en général.
Adrian Ludwin

Réponses:

67

De cette façon, reverse implémente sort.Interfaceet nous pouvons remplacer une méthode spécifique sans avoir à définir toutes les autres

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

Remarquez comment ici il échange (j,i)au lieu de (i,j)et c'est aussi la seule méthode déclarée pour la structure reversemême si reverseimplémentersort.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)
}

Quelle que soit la structure passée dans cette méthode, nous la convertissons en une nouvelle reversestructure.

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

La vraie valeur vient si vous pensez à ce que vous auriez à faire si cette approche n'était pas possible.

  1. Ajoutez une autre Reverseméthode au sort.Interface?
  2. Créer une autre ReverseInterface?
  3. ...?

Chacune de ces modifications nécessiterait de nombreuses autres lignes de code sur des milliers de packages souhaitant utiliser la fonctionnalité inverse standard.

fabrizioM
la source
2
donc cela vous permet de redéfinir juste certaines des méthodes d'une interface?
David 天宇 Wong
1
La partie importante est qu'il reversea un membre de type Interface. Ce membre a alors ses méthodes appelables sur la structure externe ou remplaçables.
Bryan
Cette fonctionnalité (ou approche) pourrait-elle être considérée comme un moyen de réaliser ce que nous faisons en Java via. extendpour étendre des sous-classes non abstraites? Pour moi, cela peut être un moyen pratique de remplacer uniquement certaines méthodes tout en utilisant les méthodes existantes implémentées par internal Interface.
Kevin Ghaboosi
C'est donc une sorte d'héritage? Et return r.Interface.Less(j, i)appelle-t-on l'implémentation parent?
warvariuc
39

Ok, la réponse acceptée m'a aidé à comprendre, mais j'ai décidé de poster une explication qui, je pense, convient mieux à ma façon de penser.

Le "Effective Go" a des exemples d'interfaces ayant intégré d'autres interfaces:

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

et une structure ayant incorporé d'autres structures:

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

Mais il n'y a aucune mention d'une structure ayant intégré une interface. J'étais confus en voyant cela dans le sortpackage:

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

...

type reverse struct {
    Interface
}

Mais l'idée est simple. C'est presque la même chose que:

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

méthodes de IntSlicepromotion reverse.

Et ça:

type reverse struct {
    Interface
}

signifie que sort.reversepeut incorporer n'importe quelle structure qui implémente l'interface sort.Interfaceet quelles que soient les méthodes de cette interface, elles seront promues reverse.

sort.Interfacea une méthode Less(i, j int) boolqui peut maintenant être remplacée:

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

Ma confusion dans la compréhension

type reverse struct {
    Interface
}

était que je pensais qu'une structure a toujours une structure fixe, c'est-à-dire un nombre fixe de champs de types fixes.

Mais ce qui suit me prouve mal:

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"})
}
warvariuc
la source
3
Si ma compréhension est correcte, les valeurs d'interface sont représentées par un pointeur vers l'instance qui lui est assignée et un pointeur vers la table de méthode du type de l'instance. Ainsi, toutes les valeurs d'interface ont la même structure en mémoire. Structurellement, l'intégration est la même que la composition. Ainsi, même une structure intégrant une interface aurait une structure fixe. Les structures des instances vers lesquelles pointe l'interface seront différentes.
Nishant George Agrwal
J'ai trouvé que c'était une meilleure réponse que celle acceptée car elle donnait beaucoup plus de détails, un exemple clair et un lien vers la documentation.
110100100 le
25

La déclaration

type reverse struct {
    Interface
}

vous permet d'initialiser reverseavec tout ce qui implémente l'interface Interface. Exemple:

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

De cette façon, toutes les méthodes implémentées par la Interfacevaleur incorporée sont remplies vers l'extérieur tandis que vous pouvez toujours en remplacer certaines reverse, par exemple Lesspour inverser le tri.

C'est ce qui se passe réellement lorsque vous utilisez sort.Reverse. Vous pouvez en savoir plus sur l'incorporation dans la section struct de la spécification .

nemo
la source
5

Je donnerai aussi mon explication. Le sortpackage définit un type non exporté reverse, qui est une structure, qui incorpore Interface.

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

Cela permet à Reverse d'utiliser les méthodes d'une autre implémentation d'interface. C'est ce qu'on appelle composition, qui est une fonctionnalité puissante de Go.

La Lessméthode pour reverseappelle la Lessméthode de la Interfacevaleur incorporée , mais avec les indices inversés, inversant l'ordre des résultats de tri.

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

Lenet Swaples deux autres méthodes de reverse, sont implicitement fournies par la Interfacevaleur d' origine car il s'agit d'un champ incorporé. La Reversefonction exportée renvoie une instance du reversetype qui contient la Interfacevaleur d' origine .

// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
    return &reverse{data}
}
Endre Simo
la source
Pour moi, cela ressemble à un héritage. "La Lessméthode pour reverseappelle la Lessméthode de la Interfacevaleur intégrée , mais avec les indices inversés, inversant l'ordre des résultats de tri." - cela ressemble à l'appel de l'implémentation parent.
warvariuc
Tant que le type reverse n'a qu'un seul champ qui implémente l'interface d'interface, il devient également membre de l'interface d'interface: 0
Allan Guwatudde
1

Je trouve cette fonctionnalité très utile lors de l'écriture de simulations dans les tests .

Voici un exemple:

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

En utilisant:

type storeMock struct {
    Store
    ...
}

Il n'est pas nécessaire de se moquer de toutes les Storeméthodes. Seul HealthCheckpeut être moqué, puisque seule cette méthode est utilisée dans le TestIsHealthytest.

Ci-dessous le résultat de la testcommande:

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

Un exemple concret de ce cas d'utilisation que l'on peut trouver lors du test du kit AWS SDK .


Pour rendre cela encore plus évident, voici l'alternative laide - le minimum qu'il faut implémenter pour satisfaire l' Storeinterface:

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
}
Czerasz
la source