Aller au contenu
Développement medium

Les interfaces en Go

8 min de lecture

Go

Une interface décrit un comportement, pas une donnée : « tout ce qui sait faire X ». C'est le concept qui rend un code Go testable et modulaire, en le découplant des types concrets. Ce guide vous apprend à définir des interfaces, à comprendre la satisfaction implicite propre à Go, à inspecter un type avec un type switch, et à éviter le piège vicieux de l'interface nil. Public visé : vous maîtrisez les méthodes et récepteurs. Exemples testés sur Go 1.26.

  • Définir une interface et l'implémenter par satisfaction implicite.
  • Concevoir de petites interfaces côté consommateur.
  • Inspecter un type avec une type assertion et un type switch.
  • Éviter le piège de l'interface nil.

Définir une interface : la satisfaction implicite

Section intitulée « Définir une interface : la satisfaction implicite »

Une interface liste des méthodes. Un type la satisfait dès qu'il possède ces méthodes, sans le déclarer : il n'y a pas de mot-clé implements en Go. C'est la satisfaction implicite, une des grandes idées du langage.

type Notifier interface {
Notifier(message string) string
}
type Console struct{}
func (Console) Notifier(m string) string { return "console: " + m }
type Email struct{ Dest string }
func (e Email) Notifier(m string) string {
return fmt.Sprintf("email->%s: %s", e.Dest, strings.ToUpper(m))
}

Console et Email satisfont Notifier automatiquement, simplement parce qu'ils ont une méthode Notifier(string) string. Aucun lien de déclaration entre eux : on pourrait ajouter un type Slack demain sans toucher à l'interface.

L'idiome Go n'est pas de définir de grosses interfaces à côté des types, mais de petites interfaces là où on les consomme. Une fonction qui dépend d'un comportement déclare l'interface minimale dont elle a besoin.

// dépend du comportement, pas d'un type concret
func alerter(n Notifier, msg string) string {
return n.Notifier(msg)
}
func main() {
fmt.Println(alerter(Console{}, "disque plein"))
fmt.Println(alerter(Email{Dest: "ops@x"}, "disque plein"))
}

Sortie :

console: disque plein
email->ops@x: DISQUE PLEIN

alerter ne connaît ni Console ni Email : elle accepte n'importe quoi qui sait notifier. C'est ce qui la rend testable (on lui injecte un faux notifier dans un test) et extensible (on ajoute des implémentations sans la modifier). La bibliothèque standard suit ce principe : io.Writer ne déclare qu'une méthode Write, et tout l'écosystème s'y branche.

Parfois, on veut récupérer le type concret derrière une interface. La type assertion n.(Email) l'extrait ; le type switch teste plusieurs types d'un coup.

var n Notifier = Email{Dest: "a@b"}
switch v := n.(type) {
case Email:
fmt.Println("c'est un Email pour", v.Dest)
case Console:
fmt.Println("c'est une Console")
}

Le type switch est plus sûr qu'une cascade d'assertions : chaque case fournit la variable v déjà convertie au bon type. On l'utilise avec parcimonie, car dépendre du type concret va à l'encontre du découplage ; mais c'est utile pour traiter des cas particuliers.

Voici l'un des pièges les plus vicieux de Go. Une interface contient deux choses : un type et une valeur. Elle n'est nil que si les deux le sont. Renvoyer un pointeur concret nil comme interface donne une interface non-nil.

type MonErreur struct{}
func (*MonErreur) Error() string { return "boum" }
func mauvais() error {
var p *MonErreur // p est nil
return p // mais l'interface error porte le type *MonErreur
}
func main() {
fmt.Println(mauvais() == nil) // false : surprise !
}

mauvais() renvoie un pointeur nil, mais l'interface error retournée n'est pas nil car elle mémorise le type *MonErreur. Le test err == nil échoue et le code croit à une erreur inexistante. La règle : renvoyez explicitement nil (le mot-clé, pas une variable pointeur nil) quand il n'y a pas d'erreur.

Un exercice qui montre la puissance des interfaces. Cherchez la solution avant d'ouvrir la réponse.

Définissez une interface Forme avec une seule méthode Aire() float64, puis :

  • un type Cercle (champ R) et un type Rectangle (champs L, H), chacun implémentant Aire().
  • une fonction aireTotale(formes []Forme) float64 qui additionne les aires de n'importe quelles formes.

Vérifiez avec un cercle de rayon 1 et un rectangle 2×3 (total attendu : environ 9,14).

Indice : aireTotale ne doit connaître que l'interface Forme, pas les types concrets.

  • Une interface décrit un comportement (une liste de méthodes), pas une donnée.
  • La satisfaction est implicite : un type satisfait une interface dès qu'il a les bonnes méthodes, sans le déclarer.
  • Préférez de petites interfaces côté consommateur (comme io.Writer) : le code devient testable et extensible.
  • Le type switch récupère le type concret, à utiliser avec parcimonie.
  • Piège : une interface nil exige type ET valeur nil ; renvoyez le mot-clé nil, jamais un pointeur nil.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn