Les interfaces en Go

Découvrez le fonctionnement des interfaces en Go : satisfaction implicite, l’interface vide any, assertions de type et un exemple Stringer complet.

Prérequis

Objectifs pédagogiques

  • Déclarer une interface et comprendre la satisfaction implicite
  • Utiliser l’interface vide any avec les assertions de type et la forme virgule-ok
  • Voir comment les interfaces découplent le code et facilitent les tests

Une interface en Go est un ensemble de signatures de méthodes. Tout type qui possède ces méthodes satisfait automatiquement l’interface : vous décrivez ce qu’une valeur sait faire, pas ce qu’elle est.

Déclarer une interface

Vous déclarez une interface avec type ... interface et listez les méthodes qu’elle exige. Par convention, les interfaces à une seule méthode se terminent souvent par « -er ».

// Shape est tout ce qui peut renvoyer son aire.
type Shape interface {
	Area() float64
}

La satisfaction implicite

Il n’existe pas de mot-clé implements en Go. Un type satisfait une interface simplement en possédant les méthodes requises. Ce découplage permet à un type de satisfaire une interface définie plus tard, même dans un autre package.

type Circle struct {
	Radius float64
}

// Comme Circle possède une méthode Area() float64, c'EST un Shape.
func (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}

Un exemple complet

Le programme ci-dessous range différents types concrets dans un []Shape et calcule un total sans se soucier de quelle forme est laquelle. Il compile et s’exécute tel quel.

package main

import (
	"fmt"
	"math"
)

// Shape est tout ce qui peut renvoyer son aire.
type Shape interface {
	Area() float64
}

type Rectangle struct {
	Width  float64
	Height float64
}

func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

type Circle struct {
	Radius float64
}

func (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}

// totalArea fonctionne avec n'importe quel Shape, quel que soit son type concret.
func totalArea(shapes []Shape) float64 {
	var sum float64
	for _, s := range shapes {
		sum += s.Area()
	}
	return sum
}

func main() {
	shapes := []Shape{
		Rectangle{Width: 3, Height: 4},
		Circle{Radius: 5},
	}
	fmt.Printf("aire totale : %.2f\n", totalArea(shapes))
}

Son exécution affiche :

aire totale : 90.54

L’interface vide et any

Une interface sans méthode est satisfaite par tous les types. Elle s’écrit interface{}, mais depuis Go 1.18 l’alias intégré any a le même sens et se lit bien mieux.

var x any = 42              // contient un int
x = "maintenant une chaîne" // contient une string
x = []int{1, 2, 3}          // contient une slice

Utilisez any avec parcimonie : il jette l’information de type, donc vous perdez la vérification à la compilation. Préférez une interface précise dès que possible.

Les assertions de type

Pour récupérer la valeur concrète stockée dans une interface, utilisez une assertion de type. La forme virgule-ok est sûre : une mauvaise supposition met ok à false au lieu de provoquer une panique.

var i any = "bonjour"

s := i.(string)  // s == "bonjour"
n, ok := i.(int) // ok == false, n == 0 (pas de panique)

if s2, ok := i.(string); ok {
	fmt.Println("c'est une chaîne :", s2)
}

L’interface Stringer

La bibliothèque standard définit fmt.Stringer, une interface à une seule méthode String() string. Le package fmt la recherche automatiquement : l’implémenter contrôle donc l’affichage de votre type.

package main

import "fmt"

// Point implémente fmt.Stringer en définissant une méthode String.
type Point struct {
	X, Y int
}

func (p Point) String() string {
	return fmt.Sprintf("(%d, %d)", p.X, p.Y)
}

func main() {
	p := Point{X: 1, Y: 2}
	fmt.Println(p) // fmt utilise String() automatiquement
}

Ce programme affiche (1, 2).

Pourquoi les interfaces sont utiles

Comme la satisfaction est implicite, une fonction qui accepte une interface ne dépend d’aucun type concret. C’est ce découplage qui rend les interfaces si précieuses pour les tests : dans un test, vous passez un petit substitut qui satisfait l’interface, à la place de la vraie base de données ou du vrai client réseau.

Erreurs fréquentes

  • Des interfaces trop grosses. Une interface à cinq méthodes est difficile à satisfaire et à simuler. Découpez-la, ou utilisez des interfaces à une méthode.
  • Recourir à any trop tôt. Il désactive la vérification de type. Modélisez le comportement avec une vraie interface.
  • Des assertions de type qui paniquent. La forme à une valeur i.(int) panique en cas de mauvaise supposition. Utilisez la forme virgule-ok sauf certitude.
  • Une interface nil qui ne l’est pas. Une interface contenant un pointeur nil est elle-même non nil, ce qui surprend souvent lors du test err != nil.

À vous de jouer

Définissez une interface Greeter avec une méthode Greet() string, implémentez-la avec un struct Person, puis affichez le message via l’interface.

Afficher la correction
package main

import "fmt"

// Greeter est satisfaite par tout type doté d'une méthode Greet.
type Greeter interface {
	Greet() string
}

type Person struct {
	Name string
}

// Person satisfait Greeter implicitement : aucun mot-clé « implements ».
func (p Person) Greet() string {
	return "Bonjour, je m'appelle " + p.Name
}

func main() {
	var g Greeter = Person{Name: "Ada"}
	fmt.Println(g.Greet())
}

Person ne nomme jamais Greeter, et pourtant elle la satisfait juste en ayant la bonne méthode. La variable g a le type de l’interface : l’appel passe donc par l’interface. Le programme affiche Bonjour, je m'appelle Ada.

Résumé

  • Une interface est un ensemble de méthodes ; tout type qui les possède la satisfait.
  • La satisfaction est implicite : il n’y a pas de mot-clé implements.
  • any (l’interface vide) contient n’importe quelle valeur ; récupérez-la avec une assertion de type.
  • Les petites interfaces découplent le code et facilitent les tests.

Ensuite, voyez comment l’interface error sous-tend la gestion des erreurs en Go. Pour revoir les types qui implémentent les interfaces, relisez les structs et méthodes.

Votre progression est enregistrée uniquement dans ce navigateur. Aucun compte n’est requis.

Recherche

La recherche s’exécute entièrement dans votre navigateur.