Les Interfaces dans le langage de programmation Go

Ce chapitre vous explique les interfaces en GoLang. Ils permettent de créer un ensemble de signatures de méthodes qu'une structure peut implémenter. Vous allez comprendre leurs intérêts et apprendre à déclarer, accéder et modifier des interfaces dans le langage de programmation Go.

Définition

Une interface est un ensemble de signatures de méthodes qu'une structure peut implémenter. Par conséquent, l'interface définit le comportement d’une structure.

Le but principal de l'interface consiste à ne fournir que des signatures de méthodes composées :

  • Du nom de la méthode
  • Des arguments d'entrée
  • Des types de retour

Il appartient à la structure de déclarer les méthodes signées dans l’interface et de les implémenter

Démonstration step by step

Déclarer une interface

Pour déclarer une interface nous avons besoin du mot-clé type qui permet d'associer un nom à votre interface et du du mo-clé interface qui indique à votre compilateur qu'il s'agit d'une interface.

package main

import (
    "fmt"
)

type Forme interface { // création de L'interface Forme
    Air() float64 // signature de la méthode Air()
    Perimetre() float64 // signature de la méthode Perimetre()
}

func main() {
    var f Forme // Initialisation de l'interface Forme
    fmt.Println(f)
}

Comme vous pouvez le voir dans une interface on ne met que des signatures de méthodes sans aucun attributs. Pour le moment ces méthodes ne sont pas encore utilisées car pour l'instant je vous montre juste comment déclarer une interface.

Résultat :

nil

D'après le résultat ci-dessus, nous pouvons voir que la valeur de notre interface est nil car il n'existe aucune structure qui implémente l'interface Forme.

Utilisation d'une interface

Notre interface Forme possède deux signatures :

  • Air() qui retourne un type float64
  • Perimetre() qui retourne un type float64

La structure qui va implémenter l'interface Forme doit obligatoirement implémenter les méthodes Air() et Perimetre() signées dans l'interface Forme.

package main

import (
    "fmt"
)

type Forme interface {
    Air() float64
    Perimetre() float64
}

type Rectangle struct {
    largeur  float64
    longueur float64
}

/* 
Pour implémenter une interface dans Go, il suffit
d'implémente toutes les méthodes de l'interface. Ici on
implémente la méthode Air() de l'interface Forme.
 */
func (r Rectangle) Air() float64 {
	return r.largeur * r.longueur
}

/*
On implémente la méthode Perimetre() de l'interface Forme
*/
func (r Rectangle) Perimetre() float64 {
	return 2 * (r.largeur * r.longueur)
}


func main() {
    var f Forme
    f = Rectangle{5.0, 4.0} // affectation de la structure Rectangle à l'interface Forme 
    r := Rectangle{5.0, 4.0} 
    fmt.Println("Type de f :", f)
    fmt.Printf("Valeur de f : %v\n", f)
    fmt.Println("Air du rectangle r :", f.Air())
    fmt.Println("f == r ? ", f == r)
}

Il est tout à fait possible d'affecter à notre interface Forme la structure Rectangle puisque la structure Rectangle implémente l'interface Forme.

Résultat :

Type de f : {5 4}
Valeur de f : {5 4}
Air du rectangle r : 20
f == r ?  true

Sur le résultat on peut remarquer que f == r ? est à true car dans mon exemple f et r ont le même type et la même valeur.


Je vais intentionnellement supprimer la méthode Air() de la structure Rectangle pour ainsi vous prouvez qu'il est vraiment obligatoire d'implémenter toutes les méthodes de l'interface Forme.

package main

import (
	"fmt"
)

type Forme interface {
	Air() float64
	Perimetre() float64
}

type Rectangle struct {
	largeur  float64
	longueur float64
}

/* Pas de méthode Air */

func (r Rectangle) Perimetre() float64 {
	return 2 * (r.largeur * r.longueur)
}

func main() {
	var f Forme
	f = Rectangle{5.0, 4.0}
	r := Rectangle{5.0, 4.0}
	fmt.Println("Type de f :", f)
	fmt.Printf("Valeur de f : %v\n", f)
	fmt.Println("Air du rectangle r :", f.Air())
	fmt.Println("f == r ? ", f == r)
}

Erreur :

.cannot use Rectangle literal (type Rectangle) as type Forme in assignment:
Rectangle does not implement Forme (missing Air method)
invalid operation: f == r (mismatched types Forme and Rectangle)

L'erreur ci-dessus indique clairement que, pour implémenter une interface avec succès, vous devez implémenter toutes les méthodes déclarées par l'interface et notre compilateur nous le fait bien savoir on nous marquons qu'il manque la méthode Air() "missing Air method".

On va dans cet exemple créer deux structures Cercle et Rectangle qui implémente l'interface Forme :

package main

import (
	"fmt"
	"math"
)

type Forme interface {
	Air() float64
	Perimetre() float64
}

/*----------------Début Structure Rectangle----------------*/
type Rectangle struct {
	largeur  float64
	longueur float64
}

func (r Rectangle) Air() float64 {
	return r.largeur * r.longueur
}

func (r Rectangle) Perimetre() float64 {
	return 2 * (r.largeur * r.longueur)
}
/*----------------Fin Structure Rectangle----------------*/


/*----------------Début Structure Cercle-----------------*/
type Cercle struct {
	rayon float64
}

func (c Cercle) Air() float64 {
	return math.Pi * c.rayon * c.rayon
}
func (c Cercle) Perimetre() float64 {
	return 2 * math.Pi * c.rayon
}
/*----------------Fin Structure Cercle-------------------*/



//Fonction qui prend compte un paramètre de type  Cerle ou Rectangle
func AirPerimetrePresentation(f Forme) {
	fmt.Println("- Air :", f.Air())
	fmt.Println("- Perimetre :", f.Perimetre())
}

func main() {
	var r Forme = Rectangle{5.0, 4.0}
	var c Forme = Cercle{5.0}

	fmt.Println("Cercle :")
	AirPerimetrePresentation(r)
	fmt.Println("\nrectangle :")
	AirPerimetrePresentation(c)
}

Résultat :

Cercle :
- Air : 20
- Perimetre : 40

rectangle :
- Air : 78.53981633974483
- Perimetre : 31.41592653589793

Le résultat ci-dessus nous montre que l'interface Forme peut être déclaré en tant que Cercle ou Rectangle

Conclusion

Les interfaces sont une sorte de contrat, ils permettent de créer des comportements génériques: si plusieurs structures doivent obéir à des comportements particuliers, alors on créé une interface décrivant ces comportements. Ces structures devront ainsi obéir strictement aux signatures de l'interface, sans quoi la compilation ne se fera pas.

Grâce à eux on peut obtenir un code beaucoup plus claire et facile à maintenir.

Espace commentaire

Écrire un commentaire

Rejoignez la discussion

Vous devez être connecté pour poster un message.

21 commentaires

Est ce que vous pouvez presenter un cours comme cela sur JAVA, C ou C++?

03/04/2019 à 08:03
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Content que ça serve. C'est l'essence même de Go : faire simple et robuste. N'oublie pas de tester tes implémentations avec des tests unitaires.

03/04/2019 à 02:04
emile16
Membre Actif
Avatar de emile16
emile16
Membre Actif

Top le tuto. Ça m'a aidé à refactorer mon code spaghetti en utilisant le polymorphisme des interfaces.

02/04/2019 à 19:28
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui, mais attention à la sémantique. Si tu implémentes sur *Rectangle, ton interface ne sera valide que pour les pointeurs vers Rectangle. Exemple :

func (r *Rectangle) Air() float64 { return r.largeur * r.longueur }

Ici, f = Rectangle{} ne marchera pas, il faudra f = &Rectangle{}.

02/04/2019 à 12:54
guy-hebert
Membre Actif
Avatar de guy-hebert
guy-hebert
Membre Actif

Peut-on implémenter une interface via un pointeur ?

02/04/2019 à 05:19
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Vérifie que ta structure implémente bien les méthodes avec les bons types de retour. Si ton interface attend float64 et que ta méthode renvoie un int, ça ne passera jamais.

01/04/2019 à 22:30
etienne-bernard
Membre Actif
Avatar de etienne-bernard
etienne-bernard
Membre Actif

Mon IDE souligne en rouge f = Rectangle{5.0, 4.0}. Il me dit que c'est pas le bon type.

01/04/2019 à 17:12
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Parce que le but de l'interface est de garantir un comportement. Si tu appelles f.Air() et que la méthode n'existe pas, ton prog crashe en runtime. Le compilo t'évite justement ce genre de catastrophe en prod.

01/04/2019 à 10:51
alegoff
Membre
Avatar de alegoff
alegoff
Membre

Question bête : pourquoi le compilateur râle si je n'implémente pas tout ? C'est pas plus simple de laisser passer ?

01/04/2019 à 05:48
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui, c'est ce qu'on appelle l'embedding d'interfaces. Tu peux composer une interface à partir d'autres plus petites. Ça garde ton code modulaire.

01/04/2019 à 00:10

Merci pour le cours car je suis un initié dans la programmation avec le langage Goland

31/03/2019 à 22:31
claire65
Membre Actif
Avatar de claire65
claire65
Membre Actif

Est-ce qu'une interface peut hériter d'une autre interface ?

31/03/2019 à 17:53
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Exact, @5 a raison. Sans import "math", impossible d'accéder aux constantes mathématiques. Vérifie ton header.

31/03/2019 à 12:25
zlombard
Membre Actif
Avatar de zlombard
zlombard
Membre Actif

T'as bien importé le package math en haut de ton fichier ? J'ai eu le même souci au début.

31/03/2019 à 08:11
paul-roland
Membre Actif
Avatar de paul-roland
paul-roland
Membre Actif

J'essaie de compiler ton exemple avec le Cercle mais j'ai un souci avec math.Pi. Il me dit undefined.

31/03/2019 à 01:57
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est une interface vide. Elle accepte n'importe quel type puisqu'aucune méthode n'est requise. C'est utile pour de la généricité forcée, mais à utiliser avec parcimonie pour éviter de perdre le typage fort.

30/03/2019 à 18:11
gabriel-richard
Membre Actif
Avatar de gabriel-richard
gabriel-richard
Membre Actif

Je bosse sur un projet existant et je vois des interfaces vides interface{} partout. C'est quoi le délire ?

30/03/2019 à 11:38
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Non, à ne jamais faire. Une interface ne contient que des signatures de méthodes. Si tu veux stocker des données, utilise une struct. L'interface, c'est uniquement le contrat de comportement.

30/03/2019 à 07:36
caron-augustin
Membre Actif
Avatar de caron-augustin
caron-augustin
Membre Actif

J'ai une question de débutant : est-ce qu'on peut ajouter des attributs à l'interface ? Le code plante quand j'essaie.

30/03/2019 à 00:59
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est que tu as oublié une méthode. Comme expliqué, si tu rates ne serait-ce qu'une signature de l'interface Forme, le compilo bloque. Vérifie bien que Air() et Perimetre() sont présentes dans ta struct.

29/03/2019 à 19:21
letellier-simone
Membre Actif
Avatar de letellier-simone
letellier-simone
Membre Actif

Tuto utile pour débuter. Par contre, j'ai une erreur cannot use Rectangle literal au moment de l'assignation. J'ai pourtant bien défini ma structure.

29/03/2019 à 13:10

Rejoindre la communauté

Recevoir les derniers articles gratuitement en créant un compte !

S'inscrire