---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Interfaces et embarquement de structures en Go : différences avec l''héritage de classes et conception par composition'
description: Comparaison de l'héritage de classes en Java/C++ et du typage structurel des interfaces en Go, avec explication du patron de composition utilisant l'embedding de structures et la délégation de méthodes à l'aide d'exemples de code.
language: fr
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Aperçu

Les interfaces Go permettent facilement à plusieurs structures de posséder des fonctions ayant les mêmes arguments et valeurs de retour, mais cela diffère de la manière dont le mot-clé extends de Java étend et redéfinit (override) le comportement de ces fonctions internes. Il ne faut pas confondre la réutilisation de code compositionnelle de Go avec l'héritage, mais il est difficile d'en obtenir une compréhension théorique parfaite dès le départ. Explorons-la à travers des scénarios où l'on commet facilement des erreurs.

## Erreurs fréquentes

Les débutants peuvent commettre les erreurs suivantes.

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type Apple struct {
	Label string
	Name  string
	Brix  float64
}

type Watermelon struct {
	Label string
	Name  string
	Brix  float64
}

func (a *Apple) PrintAll() {
	fmt.Printf("Fruit: %s, Label: %s, Brix: %v\n", a.Name, a.Label, a.Brix)
}

const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Brix 	= 14.5;
	a.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Brix = 10;
	w.Name = "watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
}

func main() {
	fmt.Println("Inheritance test #1")
	apple := new(Apple)
	watermelon := apple
	apple.SetLabel("Apple_1")
	fmt.Println("Apple, before copied to Watermelon")
	apple.PrintAll()
	watermelon.SetLabel("WaterMelon_2")
	fmt.Println("Apple, after copied to Watermelon")
	apple.PrintAll()
	fmt.Println("Watermelon, which inherited Apple's Method")
	watermelon.PrintAll()
}
```

Ce code semble ne poser aucun problème si l'on croit que Go suit l'héritage traditionnel. Cependant, sa sortie est la suivante.

```
Inheritance test #1
Succeed: Label was  Apple_1
Apple, before copied to Watermelon
Fruit: apple, Label: Apple_1, Brix: 14.5
Failed: Label was  WaterMelon_2
Apple, after copied to Watermelon
Fruit: apple, Label: EMPTY LABEL, Brix: 14.5
Watermelon, which inherited Apple's Method
Fruit: apple, Label: EMPTY LABEL, Brix: 14.5
```

Ici, le comportement de Go devient simplement clair.

```go
watermelon := apple
```

Ce code ne convertit en aucun cas Apple en une structure Watermelon.
watermelon est simplement un pointeur vers apple.

Il faut le redire ici : **Go ne suit pas le concept traditionnel d'héritage.**

Si l'on écrit du code avec cette incompréhension, on obtient des erreurs graves, comme la création inutile de pointeurs ou la copie de fonctions vers des structures inattendues.

Alors, à quoi ressemble un code exemplaire ?

## Exemple approprié en Go

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type BaseFruit struct {
	Name  string
	Brix  float64
}

type Apple struct {
	Label string
	Fruit BaseFruit
}

type Watermelon struct {
	Label string
	Fruit BaseFruit

}

func (b *BaseFruit) PrintAll() {
	fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
}


const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Fruit.Brix 	= 14.5;
	a.Fruit.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Fruit.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", a.Fruit.Name, a.Label);
	a.Fruit.PrintAll()
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Fruit.Brix = 10;
	w.Fruit.Name = "Watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Fruit.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", w.Fruit.Name, w.Label);
	w.Fruit.PrintAll()
}

func main() {
	apple := new(Apple)
	watermelon := new(Watermelon)
	apple.SetLabel("Apple_1")
	watermelon.SetLabel("WaterMelon_2")
}
```

Cependant, il est tout de même possible de faire en sorte qu'en Go cela **ressemble** à de l'héritage.
C'est l'exemple de l'embedding anonyme. Cela est possible en déclarant la structure interne comme une structure sans nom.
Dans ce cas, on peut accéder directement aux champs de la sous-structure **sans les spécifier explicitement**.
En utilisant ce modèle de **promotion** des champs de la sous-structure vers la structure parente, la lisibilité peut parfois être améliorée. Toutefois, il est recommandé de ne pas l'utiliser lorsque la sous-structure doit être explicitement visible.

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type BaseFruit struct {
	Name  string
	Brix  float64
}

type Apple struct {
	Label string
	BaseFruit
}

type Watermelon struct {
	Label string
	BaseFruit

}

func (b *BaseFruit) PrintAll() {
	fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
}


const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Brix 	= 14.5;
	a.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", a.Name, a.Label);
	a.PrintAll()
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Brix = 10;
	w.Name = "Watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", w.Name, w.Label);
	w.PrintAll()
}

func main() {
	apple := new(Apple)
	watermelon := new(Watermelon)
	apple.SetLabel("Apple_1")
	watermelon.SetLabel("WaterMelon_2")
}
```

Dans cet exemple, on observe la différence suivante.

```go
w.PrintAll() // Appel automatique par promotion via la structure sans nom, et non w.Friut.PrintAll()
```

Les deux exemples reposent sur les points importants suivants :

- main doit rester simple, et les fonctions doivent être organisées par fonctionnalité
- Pour des structures différentes, utiliser des objets différents
- Lorsqu'un partage est nécessaire, utiliser une structure interne

Quels sont les avantages de cette philosophie de programmation ?

## Avantages

- Distinction claire entre les méthodes qui doivent être partagées et celles qui ne le doivent pas
- Séparation des responsabilités entre structures et méthodes individuelles
- Code structurellement séparé selon les spécifications des fonctionnalités requises

Au début, Go peut sembler peu familier car il diffère de la POO traditionnelle, mais une fois habitué, il permet une programmation explicite.

## Résumé

- Isolez les responsabilités
- Découpez en détail, structure par structure
- Il ne faut pas comprendre les méthodes comme des classes abstraites de Java
- Pratiquez une programmation explicite et concrète
  Go doit être traité de manière plus simple, plus claire et plus indépendante que le modèle POO traditionnel. Plutôt que de programmer de manière extensible, écrivons du code séparé de façon progressive et structurée.
