Interfaces en Go

Qu'est-ce qu'une interface ?

Une interface en Go est définie comme un ensemble de signatures de méthodes. Ces signatures spécifient les valeurs d'entrée ainsi que celles de sortie que différents types de données ou structures doivent respecter pour se conformer à l'interface. Pour qu'un type ou une structure implémente cette interface, il est impératif qu'il satisfasse toutes les signatures de méthodes énoncées.

Définir une interface

Vous pouvez créer une interface en utilisant la syntaxe suivante :

type NomInterface interface {
  // Signatures des méthodes
}

Il est important de noter que les noms d'interfaces conventionnellement utilisent le format camelCase ou PascalCase. Également, les interfaces qui contiennent une seule méthode sont souvent suffixées par le terme -er.

Exemple de mise en œuvre d'une interface

Considérons l'exemple suivant :

package main
import (
  "fmt"
)
type mariage interface {
  amour()
  arrange()
}
type john struct{}
func (a *john) amour() {
  fmt.Println("amour avec john")
}
func (a *john) arrange() {
  fmt.Println("mariage arrangé avec john")
}
func nouvelleVie(gars mariage) {
  gars.amour()
  gars.arrange()
}
func main() {
  nouvelleVie(&john{})
}

Dans cet exemple : - L'interface mariage possède deux méthodes : amour et arrange. - La structure john implémente l'interface mariage sans avoir à la déclarer explicitement. - Tant que la structure comprend toutes les méthodes de l'interface, elle est considérée comme étant une implémentation valide. - La fonction nouvelleVie() accepte un argument de type mariage.

Avantages des interfaces

Utiliser une interface offre une grande flexibilité, car tout type de structure qui implémente l'interface mariage peut être transmis à la fonction nouvelleVie(). Cela signifie que la structure john peut avoir ses méthodes amour et arrange modifiées sans que cela n'impacte la fonction nouvelleVie(). Tant que l'interface mariage est toujours respectée, toute structure modifiée sera valide pour être utilisée avec nouvelleVie(). Le compilateur de Go s'assurera que toutes les méthodes de l'interface soient bien implémentées.

Interface vide

Une interface sans méthodes est appelée interface vide.

type interfaceVide interface {}

L'inconvénient d'une interface vide réside dans la perte de sécurité de type. Comme il n'y a pas de méthodes, il est possible de passer n'importe quel type à une fonction qui accepte une interface vide.

Exemple de l'interface vide

La fonction Println() du package fmt accepte une interface vide en argument. Voici comment cette fonction est définie :

// Println formate en utilisant les formats par défaut pour ses opérandes et écrit dans la sortie standard.
// Des espaces sont toujours ajoutés entre les opérandes et une nouvelle ligne est ajoutée.
// Elle retourne le nombre de bytes écrits et toute erreur d'écriture rencontrée.
func Println(a ...interface{}) (n int, err error) {
    return Fprintln(os.Stdout, a...)
}

En raison de l'interface vide, n'importe quel type peut être passé à la fonction Println().

Embarquer une interface

Une interface en Go peut être intégrée à d'autres interfaces ou structures. Voici un exemple :

package main
import "fmt"
// Tout type devant implémenter mariage doit également implémenter toutes les méthodes de dating.
type mariage interface {
    dating
    amour()
    arrange()
}
type dating interface {
    compatibilite()
}
type john struct {
    age int
}
func (a john) amour() {
    fmt.Println("amour avec john")
}
func (a john) arrange() {
    fmt.Println("mariage arrangé avec john")
}
func (a john) compatibilite() {
    fmt.Println("compatible avec john")
}
type parents struct {
    m     mariage
    citation string
}
type lovers struct {
    mariage
    citation string
}
func main() {
    a := john{age: 24}
    p := parents{citation: "Je t'ai vu et tu étais parfait, alors je t'ai aimé. Puis j'ai vu que tu n'étais pas parfait et je t'ai aimé encore plus.", m: a}
    p.m.amour()
    p.m.compatibilite()
    p.m.arrange()
    l := lovers{citation: "Je t'ai vu et tu étais parfait...", mariage: a}
    fmt.Println("\n", l.citation, "\n")
    l.amour()
    l.compatibilite()
    l.arrange()
}

Dans ce cas, nous avons plusieurs types intégrés qui exploitent l'interface mariages.

Exemple Codebyte

Un exemple simple d'utilisation des interfaces est le suivant, où un carré et un cercle implémentent tous deux l'interface géométrie.

package main
import "fmt"
type geometrie interface {
    aire() float64
    perimeter() float64
}
type carre struct { 
    cote float64
}
func (c carre) aire() float64 {
    return c.cote * c.cote
}
func (c carre) perimeter() float64 {
    return 4 * c.cote
}

type cercle struct {
    rayon float64
}
func (c cercle) aire() float64 {
    return 3.14 * c.rayon * c.rayon
}
func (c cercle) perimeter() float64 {
    return 2 * 3.14 * c.rayon
}

func imprimerGeometrie(g geometrie) {
    fmt.Printf("Aire: %v, Périmètre: %v\n", g.aire(), g.perimeter())
}

func main() {
    c := carre{cote: 10}
    circ := cercle{rayon: 5}
    imprimerGeometrie(c)
    imprimerGeometrie(circ)
}

Dans ce code, l'interface géométrie a deux méthodes : aire() et perimeter(), permettant ainsi d'utiliser indistinctement des objets géométriques.