CodeGym /Cours /C# SELF /La notion de polymorphisme et son rôle en POO

La notion de polymorphisme et son rôle en POO

C# SELF
Niveau 21, Leçon 0
Disponible

1. Intro

Imagine : chez toi, t'as une télé, une chaîne hi-fi, une clim et une ampoule connectée. Et chacun a sa propre télécommande. Pour allumer la télé, tu prends la télécommande de la télé et tu appuies sur le bouton "Allumer". Pour allumer la chaîne hi-fi, tu prends la télécommande de la chaîne et tu appuies sur le même bouton "Allumer". Tu vois le truc ? C'est le bazar, non ?

Mais imagine si t'avais une télécommande universelle ? Tu la prends, tu appuies sur "Allumer", et elle pige toute seule quel appareil il faut allumer, et elle lui envoie la bonne commande. Et la télécommande, elle sait même pas comment exactement on allume la télé ou comment exactement on allume la chaîne hi-fi. Elle sait juste que tous ces appareils ont une "fonction d'allumage" commune.

Voilà, c'est ça l'analogie avec le polymorphisme en prog !

Polymorphisme

Le mot "polymorphisme" vient du grec poly (plusieurs) et morph (forme). Littéralement, ça veut dire "plusieurs formes". En POO, c'est la capacité d'un objet à prendre différentes formes ou, plus précisément, la capacité d'une même méthode à se comporter différemment selon le type d'objet sur lequel elle est appelée.

En C#, le polymorphisme est surtout obtenu grâce à l'héritage et à l'utilisation des méthodes virtual et override.

L'idée clé du polymorphisme, c'est le upcasting (conversion ascendante). C'est quoi ce truc ?

Revenons à notre hiérarchie Animal → Dog, Cat. On sait qu'un chien est un animal. Un chat est un animal. C'est la relation "is-a" — la base de l'héritage.


public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Un certain son...");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Ouaf-ouaf !");
    }
}

public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Miaou !");
    }
}

Grâce à la relation "is-a", on peut assigner un objet d'une classe dérivée à une variable du type de la classe de base. Ça s'appelle le upcasting (conversion ascendante), parce qu'on "monte" l'objet vers un type plus général. Le compilateur C# laisse faire ça direct :


// On a un chien concret
Dog myDog = new Dog();

// On peut assigner l'objet Dog à une variable de type Animal !
// C'est ça le upcasting.
// À droite - un Dog concret, à gauche - un Animal plus général.
Animal generalAnimal = myDog;

// Et là, on peut appeler MakeSound() !
generalAnimal.MakeSound(); // Affiche "Ouaf-ouaf !" (Pas "Un certain son...", mais bien "Ouaf-ouaf !")               

Qu'est-ce qui s'est passé ici ?

  1. Quand on a écrit Animal generalAnimal = myDog;, on n'a pas créé un nouvel animal. On a juste pris l'objet myDog (qui est en vrai un Dog) et on l'a mis dans une "boîte" étiquetée Animal.
  2. À l'étape de compilation (quand le code devient du bytecode), la variable generalAnimal est de type Animal. Donc le compilateur "sait" qu'elle peut appeler que les méthodes et propriétés du Animal.
  3. Mais le plus cool : à l'exécution (runtime), quand on fait generalAnimal.MakeSound(), le runtime .NET regarde pas le type de la variable (Animal), mais le type réel de l'objet dedans (Dog) ! Et comme la méthode MakeSound() est marquée virtual dans Animal et override dans Dog, c'est bien l'implémentation de Dog qui est appelée.

Cette magie, où le comportement de la méthode dépend du type réel de l'objet à l'exécution et pas du type de la variable, ça s'appelle la dispatch dynamique ou polymorphisme d'exécution.

Imagine une boîte : dessus, c'est marqué "Fruit" (notre type de variable Animal). Dedans, tu mets une pomme (notre objet Dog). Quand tu demandes "Fruit, fais un bruit !" (appel de MakeSound()), la boîte, sachant qu'il y a une pomme dedans, fait "crunch", pas juste un bruit "fruit" générique.

2. Démo du polymorphisme en action

Le meilleur moyen de piger le polymorphisme, c'est de le voir en vrai, surtout quand t'as pas qu'un seul objet mais tout un lot.

On va upgrader notre appli "Mon petit zoo". Imagine qu'on bosse dans une clinique véto, et qu'on reçoit plein d'animaux à examiner. On veut que chaque examen suive ses propres règles, mais que le code pour lancer l'examen soit universel.

D'abord, on ajoute à notre classe de base Animal et à ses classes dérivées Dog et Cat une nouvelle méthode virtuelle Examine() :


public class Animal
{
    public string Name { get; set; }
    public Animal(string name) { Name = name; }
    public virtual void MakeSound() { Console.WriteLine($"{Name} fait un certain son..."); }
    public virtual void Examine()
    {
        Console.WriteLine($"Examen de {Name} :");
        Console.WriteLine("  - Vérification de la respiration.");
        Console.WriteLine("  - Prise de température.");
    }
}

public class Dog : Animal
{
    public Dog(string name) : base(name) { }
    public override void MakeSound() { Console.WriteLine($"{Name} dit : Ouaf !"); }
    public override void Examine()
    {
        base.Examine();
        Console.WriteLine("  - Vérification des dents.");
        Console.WriteLine("  - Vaccin contre la rage.");
    }
}

public class Cat : Animal
{
    public Cat(string name) : base(name) { }
    public override void MakeSound() { Console.WriteLine($"{Name} dit : Miaou !"); }
    public override void Examine()
    {
        base.Examine();
        Console.WriteLine("  - Vérification des griffes.");
        Console.WriteLine("  - Comprimé contre les vers.");
    }
}

Maintenant, on a des méthodes Examine() spécialisées pour les chiens et les chats. Check le base.Examine(); dans les méthodes overridées. Ça permet de faire d'abord les étapes communes (de la classe de base Animal), puis d'ajouter les trucs spécifiques à chaque animal. Trop pratique !

Maintenant, imagine notre clinique véto. On a une liste d'animaux venus pour un check-up :


class Program
{
    static void Main()
    {
        Animal[] animals = {
            new Dog("Rex"),
            new Cat("Kitty"),
            new Animal("Lapin")
        };

        Console.WriteLine("--- Examen des animaux ---");
        foreach (Animal animal in animals)
        {
            animal.Examine();
            Console.WriteLine();
        }

        Console.WriteLine("--- L'heure des voix ---");
        foreach (Animal animal in animals)
            animal.MakeSound();
    }
}

Résultat :


--- Examen des animaux ---
Examen de Rex :
  - Vérification de la respiration.
  - Prise de température.
  - Vérification des dents.
  - Vaccin contre la rage.

Examen de Kitty :
  - Vérification de la respiration.
  - Prise de température.
  - Vérification des griffes.
  - Comprimé contre les vers.

Examen de Lapin :
  - Vérification de la respiration.
  - Prise de température.

--- L'heure des voix ---
Rex dit : Ouaf !
Kitty dit : Miaou !
Lapin fait un certain son...

Voilà la puissance du polymorphisme ! On a écrit une seule boucle foreach (Animal animal in animals), et à chaque tour, on appelle la même méthode animal.Examine() (ou animal.MakeSound()). Mais à chaque fois, c'est la bonne implémentation spécifique qui s'exécute pour le chien, le chat ou juste l'animal générique. Le code reste simple, propre et universel, et les détails sont cachés dans chaque classe.

Si demain on reçoit un hamster Hamster, il suffit de créer la classe Hamster : Animal, d'override Examine(), et il s'intégrera direct dans notre système d'examen véto sans rien changer à la boucle foreach ! C'est ça, la vraie force du polymorphisme.

3. Le rôle du polymorphisme en POO

Le polymorphisme, c'est pas juste un mot stylé ou un tour de passe-passe. C'est un principe fondamental qui rend tes programmes flexibles, évolutifs et faciles à maintenir. Voyons à quoi ça sert :

Flexibilité et évolutivité (Principe Open/Closed)

C'est sûrement le plus gros avantage. Le polymorphisme te permet d'écrire du code ouvert à l'extension, mais fermé à la modification. Ça veut dire quoi ?

  • Ouvert à l'extension : Tu peux ajouter de nouveaux types d'animaux (genre Bird, Fish, Hamster) juste en créant de nouvelles classes qui héritent de Animal et override les méthodes qu'il faut.
  • Fermé à la modification : T'as pas besoin de toucher au code existant qui bosse avec Animal[]. La boucle foreach qui appelle animal.Examine() reste 100% la même, peu importe combien de nouveaux animaux tu ajoutes.

Imagine combien de if-else if ou switch t'aurais à écrire et à modifier tout le temps s'il n'y avait pas le polymorphisme ! Ce serait l'enfer.

Simplification du code

Le polymorphisme simplifie grave le code qui bosse avec des collections d'objets différents mais liés. Au lieu de checker le type de chaque objet et d'appeler la bonne méthode, tu appelles juste la méthode commune de la classe de base, et le système se débrouille pour appeler la bonne implémentation.

C'est comme avoir un seul bouton "Ouvrir" pour toutes les portes : normale, coulissante, tournante. Pas besoin de tirer, pousser ou tourner — juste "ouvrir", et chaque porte s'ouvre à sa façon.

Abstraction

Le polymorphisme est super lié à l'abstraction, un autre pilier de la POO dont on parlera plus en détail dans les prochaines confs. Ça te permet de te concentrer sur le "quoi" fait l'objet (genre "fait un bruit", "se fait examiner") au lieu du "comment" il le fait. Tu bosses avec l'idée abstraite "animal", pas avec le "chien" ou le "chat" concret. Ça te permet d'écrire du code haut niveau, propre, qui dépend pas des détails d'implémentation.

Réutilisation du code

Même si l'héritage permet déjà de réutiliser du code via les propriétés et méthodes de la classe de base, le polymorphisme va plus loin. Il permet de créer des algos et structures de données universels (genre Animal[]) qui peuvent manipuler n'importe quel objet hérité de la classe de base, sans dupliquer la logique pour chaque type dérivé.

En gros, le polymorphisme rend ton code plus "pro" et prêt à évoluer, ce qui est super important en vrai dev, où les besoins changent tout le temps.

4. Quelques subtilités utiles

Schéma du polymorphisme

classDiagram
    class Animal {
        +MakeSound()
    }
    class Dog {
        +MakeSound()
    }
    class Cat {
        +MakeSound()
    }
    class Parrot {
        +MakeSound()
    }

    Animal <|-- Dog
    Animal <|-- Cat
    Animal <|-- Parrot
Schéma d'héritage des classes pour le polymorphisme

Quand tu appelles MakeSound() via une référence de type Animal — c'est la méthode de la vraie classe de l'objet qui sera appelée.

Utiliser le polymorphisme avec des tableaux et des collections

C'est super courant de devoir parcourir une collection d'entités différentes et de "lancer" la même logique sur toutes.


// Dans notre appli d'entraînement :
Animal[] zoo = { new Dog("Rex"), new Cat("Tom"), new Parrot("Polly") };

foreach (Animal animal in zoo)
{
    animal.MakeSound();
}

Dans les vrais projets, on fait ça pour gérer des events, dessiner à l'écran (chaque forme à sa façon), gérer des paiements (différents types de cartes et services), etc.

Comment marche le polymorphisme

Type de l'objet Type de la variable Quelle méthode est appelée ?
Dog
Animal
Dog.MakeSound()
Cat
Animal
Cat.MakeSound()
Parrot
Animal
Parrot.MakeSound()
Animal
Animal
Animal.MakeSound()

Le plus important — la méthode doit être virtual ou abstract dans la classe de base et override dans la classe dérivée !

5. Erreurs classiques avec le polymorphisme

Dans la vraie vie, les étudiants font souvent ces erreurs :

  • Tu oublies de mettre la méthode virtual dans la classe de base — et au lieu du polymorphisme, t'as toujours le même comportement.
  • Tu te mélanges : quelle classe pour la variable ? Retiens : la variable a un type (genre Animal), et l'objet que tu mets dedans — c'est une instance (genre new Dog()).
  • Tu veux utiliser des membres nouveaux qui n'existent pas dans la classe de base via une variable du type de base. Par exemple :

Animal pet = new Dog();
pet.Bark(); // Erreur ! Y'a pas Bark() dans Animal

Comment gérer ça ? Si t'en as vraiment besoin, utilise un cast, mais essaie de structurer ton code pour bosser seulement avec les méthodes qui existent dans la classe de base.

2
Mission
C# SELF, niveau 21, leçon 0
Bloqué
Démonstration simple du polymorphisme
Démonstration simple du polymorphisme
2
Mission
C# SELF, niveau 21, leçon 0
Bloqué
Inspection des "animaux"
Inspection des "animaux"
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION