Menu
Coddy logo textTech

Pourquoi Go n'a pas d'héritage

Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 34 sur 107.

Les langages orientés objet traditionnels comme Java et C++ utilisent l'héritage pour partager du code entre les types. Une classe enfant étend une classe parente, héritant de tous ses champs et méthodes. Go omet délibérément cette fonctionnalité.

L'héritage crée un couplage fort entre les types. Lorsqu'une classe parente change, toutes les classes enfants sont affectées.

Les hiérarchies d'héritage profondes deviennent difficiles à comprendre et à maintenir. Le « problème de la classe de base fragile » survient lorsque des modifications apportées à une classe de base cassent de manière inattendue les classes dérivées.

Les concepteurs de Go ont choisi une voie différente : la composition plutôt que l'héritage. Au lieu de dire « un Dog est un Animal », Go vous encourage à dire « un Dog a des comportements de type Animal ». Ce changement subtil mène à un code plus flexible et plus facile à maintenir.

Go permet la réutilisation de code grâce à deux mécanismes que vous avez déjà appris :

  • Interfaces définissent des contrats de comportement sans détails d'implémentation
  • L'imbrication de structures permet aux types d'inclure d'autres types et de réutiliser leurs méthodes

Considérez cette comparaison. En POO traditionnelle, vous pourriez écrire class Dog extends Animal. En Go, vous imbriquez une struct Animal à l'intérieur de Dog et implémentez des interfaces partagées. Le résultat est une fonctionnalité similaire avec un couplage plus lâche entre les types.

Ce chapitre explore l'imbrication de structs en profondeur, montrant comment Go tire parti des avantages de l'héritage sans ses inconvénients.

challenge icon

Défi

Facile

Construisons un système de notification qui démontre l'approche de composition de Go au lieu de l'héritage. Vous allez créer des types qui partagent un comportement via des interfaces et l'imbrication de structures (struct embedding) plutôt que par des hiérarchies de classes.

Vous organiserez votre code sur trois fichiers :

  • notifier.go : Définissez une interface Notifier avec une seule méthode Notify(message string) string. Créez également une structure BaseNotifier avec un champ Name qui sera imbriqué par d'autres types. Donnez à BaseNotifier une méthode appelée Format(message string) string qui retourne le message préfixé par le nom du notificateur entre crochets.
  • channels.go : Créez deux types de canaux de notification qui imbriquent BaseNotifier et implémentent l'interface Notifier :
    • EmailNotifier avec un champ supplémentaire Address
    • SMSNotifier avec un champ supplémentaire Phone
    La méthode Notify de chaque type doit utiliser la méthode Format imbriquée et inclure ses informations de canal spécifiques dans la sortie.
  • main.go : Créez une fonction appelée SendAlert qui accepte n'importe quel Notifier et un message, puis retourne le résultat de l'appel à Notify. Lisez les détails de la notification depuis l'entrée, créez les deux types de notificateurs et démontrez comment ils peuvent être utilisés de manière interchangeable via l'interface.

Les entrées suivantes seront fournies :

  • Ligne 1 : Nom du notificateur pour l'e-mail
  • Ligne 2 : Adresse e-mail
  • Ligne 3 : Nom du notificateur pour le SMS
  • Ligne 4 : Numéro de téléphone
  • Ligne 5 : Message d'alerte

Votre méthode Format sur BaseNotifier doit retourner :

[Name] message

Vos méthodes Notify doivent retourner :

  • EmailNotifier : Email to [Address]: [formatted message]
  • SMSNotifier : SMS to [Phone]: [formatted message]

Par exemple, avec Alerts, user@mail.com, Urgent, 555-1234, et Server down, votre sortie devrait être :

Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down

Remarquez comment les deux types de notificateurs réutilisent la méthode Format de BaseNotifier grâce à l'imbrication, tandis que chacun fournit sa propre implémentation de Notify. La fonction SendAlert fonctionne avec n'importe quel Notifier sans connaître le type concret — c'est la composition plutôt que l'héritage en action.

Aide-mémoire

Go utilise la composition plutôt que l'héritage pour partager du code entre les types, évitant ainsi le couplage fort et les problèmes de classe de base fragile de l'héritage orienté objet traditionnel.

Au lieu des hiérarchies de classes, Go parvient à la réutilisation du code via :

  • Les interfaces - définissent des contrats de comportement sans détails d'implémentation
  • L'imbrication de structures (Struct embedding) - permet aux types d'inclure d'autres types et de réutiliser leurs méthodes

Plutôt que de dire « un Chien est un Animal » (héritage), Go encourage « un Chien a des comportements de type Animal » (composition).

Exemple d'imbrication de structures

Définissez une structure de base qui sera imbriquée :

type BaseNotifier struct {
    Name string
}

func (b BaseNotifier) Format(message string) string {
    return "[" + b.Name + "] " + message
}

Imbriquez la structure de base dans d'autres types pour réutiliser ses champs et ses méthodes :

type EmailNotifier struct {
    BaseNotifier  // structure imbriquée
    Address string
}

func (e EmailNotifier) Notify(message string) string {
    // Peut appeler la méthode Format imbriquée directement
    return "Email to " + e.Address + ": " + e.Format(message)
}

Définissez une interface que plusieurs types peuvent implémenter :

type Notifier interface {
    Notify(message string) string
}

Utilisez l'interface pour travailler avec différents types concrets de manière interchangeable :

func SendAlert(n Notifier, message string) string {
    return n.Notify(message)
}

Cette approche offre une fonctionnalité similaire à l'héritage mais avec un couplage plus lâche entre les types.

Essayez vous-même

package main

import (
	"bufio"
	"fmt"
	"os"
)

// SendAlert accepte n'importe quel Notifier et un message, et retourne le résultat de l'appel à Notify
// TODO: Implémenter la fonction SendAlert

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Lire le nom du notificateur d'e-mail
	scanner.Scan()
	emailName := scanner.Text()
	
	// Lire l'adresse e-mail
	scanner.Scan()
	emailAddress := scanner.Text()
	
	// Lire le nom du notificateur SMS
	scanner.Scan()
	smsName := scanner.Text()
	
	// Lire le numéro de téléphone
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// Lire le message d'alerte
	scanner.Scan()
	alertMessage := scanner.Text()
	
	// TODO: Créer un EmailNotifier avec emailName et emailAddress
	
	// TODO: Créer un SMSNotifier avec smsName et phoneNumber
	
	// TODO: Utiliser SendAlert pour envoyer le alertMessage via les deux notificateurs
	// et afficher les résultats
	
	// Supprimer les avertissements de variables inutilisées (les retirer lors de l'utilisation des variables)
	_ = emailName
	_ = emailAddress
	_ = smsName
	_ = phoneNumber
	_ = alertMessage
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet