Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
Dart

L’héritage des classes Dart

Un cours complet pour maîtriser L’héritage des classes Dart.

0 chapitres 0 QCM Accès gratuit

Objectifs du cours

  • À la fin du cours, vous serez capables de créer une classe dérivée en Dart à partir d'une classe existante en utilisant le mot-clé extends.
  • Utiliser adéquatement le mot-clé super de manière appropriée pour appeler les constructeurs des classes parentes lors de la création de classes dérivées.

introduction-a-la-programmation-orientee-objet-dans-dart

  1. Introduction à l'Héritage en Dart

    • En programmation, l'héritage est un concept fondamental qui permet la création de nouvelles classes en se basant sur des classes existantes. En Dart, un langage de programmation développé par Google, l'héritage est pris en charge de manière similaire à d'autres langages orientés objet tels que Java ou C++.
    • L'héritage est le mécanisme par lequel on créé une classe B à partir d’une autre classe existante A.
    • On dit alors que la classe B hérite de la classe A. Ainsi, la classe A est appelé "classe parente" ou "super classe" et la classe B est la "classe enfant" ou "sous classe". L'héritage en Dart se fait en utilisant le mot clé extends.
    • L'héritage est la pierre angulaire de la programmation objet. En utilisant le mot clé extends, vous pouvez permettre à une classe enfant d’hériter les propriétés et les méthodes d’une autre classe parente. De plus, la classe enfant a accès aux propriétés et méthodes de sa propre classe ainsi qu’à la classe de base.
    • En fait, dans le langage Dart, la classe Objet est la Super classe (parente) de toutes les classes. Par conséquent, chaque classe est dérivée d’au moins une classe ou a au moins une classe parente à l’exception de la classe Object.
  2. Utilisation de l'Héritage en Dart

    • En Dart, l'héritage est utilisé pour créer une relation de parenté entre les classes. La classe parente, également appelée superclasse ou classe de base, définit le comportement et les caractéristiques communes à un groupe de classes dérivées, appelées sous-classes ou classes dérivées.
    • Les sous-classes héritent des membres (attributs et méthodes) de la superclasse et peuvent ajouter leurs propres membres spécifiques.
    • Dans Dart, l'héritage est implémenté via le mot-clé 'extends'. Lorsqu'une classe est déclarée hériter d'une autre classe, elle devient une sous-classe et la classe dont elle hérite devient la superclasse.
    1. Définition d'une classe de base (superclasse) :
      • En Dart, une classe de base, également appelée superclasse, est une classe dont d'autres classes peuvent hériter. Elle sert souvent de modèle ou de point de départ pour créer de nouvelles classes.
      • // Déclaration de la classe Animal.
        class Animal {
          // Propriété pour stocker le nom de l'animal.
          String nom;
        
          // Propriété pour stocker l'âge de l'animal.
          int age;
        
          // Constructeur de la classe Animal.
          // Il prend le nom et l'âge en paramètres et initialise les propriétés correspondantes.
          Animal(this.nom, this.age);
        
          // Méthode faireDuBruit.
          // Affiche un message indiquant que certains animaux font du bruit.
          void faireDuBruit() {
            print("Certains animaux font du bruit.");
          }
        }	
      • Dans cet exemple, Animal est une classe de base avec deux propriétés (nom et age) et une méthode (faireDuBruit).
    2. Définition d'une sous-classe qui hérite de la superclasse
      • Pour hériter d'une classe de base en Dart, on utilise le mot-clé extends. Par exemple, voici comment créer une classe Chien qui hérite de la classe Animal :
      • // Déclaration de la classe Chien qui étend la classe Animal.
        class Chien extends Animal {
          // Constructeur de la classe Chien.
          // Appelle le constructeur de la classe parente (Animal) en utilisant 'super'.
          Chien(String nom, int age) : super(nom, age);
        
          // Méthode aboyer.
          // Affiche un message indiquant que le chien aboie, en utilisant le nom de l'animal.
          void aboyer() {
            print("$nom aboie : Woof! Woof!");
          }
        }
        
      • La classe Chien hérite de la classe Animal. Elle utilise le mot-clé extends suivi du nom de la classe parente. Le constructeur de la classe fille utilise super pour appeler le constructeur de la classe parente avec les paramètres nécessaires.
    3. Instanciation et utilisation de la sous-classe
        void main() {
          var monChien = Chien("Buddy", 3);
        
          print("Nom du chien : ${monChien.nom}");
          print("Age du chien : ${monChien.age}");
        
          monChien.faireDuBruit(); // Appelle la méthode de la classe parente
          monChien.aboyer(); // Appelle la méthode spécifique à la classe fille
        }
      • Lorsque vous exécutez le code ci-dessus, vous obtiendrez la sortie suivante :
      Nom du chien : Buddy
      Age du chien : 3
      Certains animaux font du bruit.
      Buddy aboie : Woof! Woof!
    4. Ce code montre comment une classe dérivée (Chien) peut hériter des propriétés et des méthodes d'une classe de base (Animal) en Dart. L'héritage permet la réutilisation du code et la création de hiérarchies de classes.
    5. Exemple
      • Structure de l'exemple
      • lib/ ├── models/ │ ├── animal.dart // class Animal │ └── chien.dart // class Chien extends Animal ├── services/ │ └── zoo_service.dart // class ZooService └── main.dart // import 'models/animal.dart';
        • lib/models/animal.dart
        • // Classe parente - représente un animal générique
          class Animal {
            final String nom;
            final int age;
          
            // Constructeur principal
            Animal(this.nom, this.age);
          
            // Méthode commune à tous les animaux
            void sePresenter() {
              print('Je suis $nom, j\'ai $age ans.');
            }
          
            // Méthode à potentiellement surcharger
            void manger() {
              print('$nom mange de la nourriture.');
            }
          
            // Getter utile pour les sous-classes
            String get description => '$nom ($age ans)';
          }
      • lib/models/chien.dart
          import 'animal.dart';
          
          // Classe fille qui hérite de Animal
          class Chien extends Animal {
            final String race;
          
            // ✅ Constructeur moderne avec super parameters (Dart 2.17+)
            Chien(super.nom, super.age, this.race);
          
            // ? Surcharge de la méthode manger()
            @override
            void manger() {
              super.manger(); // Appel optionnel à la version parente
              print('$nom préfère les croquettes pour chien !');
            }
          
            // Méthode spécifique à Chien
            void aboyer() {
              print('$nom aboie : Ouaf ouaf !');
            }
          
            // Surcharge de toString() pour un debug plus clair
            @override
            String toString() {
              return 'Chien{nom: $nom, age: $age, race: $race}';
            }
          }
      • lib/services/zoo_service.dart
          import '../models/animal.dart';
          import '../models/chien.dart';
          
          // Service qui gère une collection d'animaux
          class ZooService {
            final String nomZoo;
            final List<Animal> _animaux = [];
          
            ZooService(this.nomZoo);
          
            // Ajouter un animal au zoo
            void ajouterAnimal(Animal animal) {
              _animaux.add(animal);
              print('✅ ${animal.nom} a été ajouté au zoo $nomZoo');
            }
          
            // Faire manger tous les animaux (polymorphisme)
            void faireMangerTousLesAnimaux() {
              print('\n?️ Heure du repas au zoo $nomZoo :');
              for (final animal in _animaux) {
                animal.manger(); // Appel polymorphe : version adaptée à chaque type
              }
            }
          
            // Lister tous les animaux
            void listerAnimaux() {
              print('\n? Animaux du zoo $nomZoo :');
              for (final animal in _animaux) {
                animal.sePresenter();
                // Test de type pour accéder aux méthodes spécifiques
                if (animal is Chien) {
                  animal.aboyer();
                }
              }
            }
          
            // Getter pour accéder aux animaux (en lecture seule)
            List<Animal> get animaux => List.unmodifiable(_animaux);
          }
      • lib/main.dart
          import 'models/animal.dart';
          import 'models/chien.dart';
          import 'services/zoo_service.dart';
          
          void main() {
            print('? Démarrage de l\'application Zoo\n');
          
            // ? Création d'animaux
            final Animal chat = Animal('Minou', 3);
            final Chien rex = Chien('Rex', 5, 'Berger Allemand');
            final Chien medor = Chien('Médor', 2, 'Labrador');
          
            // ? Initialisation du service Zoo
            final ZooService monZoo = ZooService('Zoo Central');
          
            // ? Ajout des animaux
            monZoo.ajouterAnimal(chat);
            monZoo.ajouterAnimal(rex);
            monZoo.ajouterAnimal(medor);
          
            // ? Démonstration du polymorphisme
            monZoo.faireMangerTousLesAnimaux();
          
            // ? Lister les animaux avec leurs spécificités
            monZoo.listerAnimaux();
          
            // ? Accès direct à un chien pour méthode spécifique
            print('\n? Focus sur ${rex.nom} :');
            rex.aboyer();
            print('Description : ${rex.description}');
          }
  3. Déclarer un constructeur d'une sous-classe (ou classe dérivée)

    • En Dart, pour déclarer un constructeur d'une sous-classe (ou classe dérivée), vous utilisez le mot-clé super pour appeler le constructeur de la superclasse. Voici comment vous pouvez déclarer un constructeur pour une sous-classe :
    • Supposons que nous ayons une classe de base Animal :
    • class Animal {
        String name;
        int age;
      
        Animal(this.name, this.age);
      
        void eat() {
          print('$name is eating.');
        }
      
        void sleep() {
          print('$name is sleeping.');
        }
      }
    • Et nous avons une sous-classe Dog qui hérite de la classe Animal :
    • class Dog extends Animal {
        String breed;
      
        // Constructeur de la sous-classe Dog
        Dog(String name, int age, this.breed) : super(name, age);
      
        void bark() {
          print('$name is barking.');
        }
      }
    • Dans cet exemple, le constructeur de la sous-classe Dog est déclaré avec la syntaxe suivante :
    • Dog(String name, int age, this.breed) : super(name, age);
    • Explications :
      • Dog est le nom du constructeur.
      • String name, int age, this.breed sont les paramètres du constructeur de la sous-classe.
      • : super(name, age); est l'appel au constructeur de la superclasse Animal avec les arguments name et age. Cela permet d'initialiser les propriétés name et age de la superclasse.
  4. Applications

    1. Act01: Gestion d'un Parc de Véhicules
      • Enoncé
        • Vous devez concevoir une application de gestion de véhicules en utilisant l'héritage de classes et l'organisation modulaire en Dart. Le programme doit compiler et s'exécuter sans erreur.
      • Architecture
        • Créez exactement cette structure de fichiers dans votre projet :
            lib/ ├── models/ │ ├── vehicule.dart // Classe de base │ ├── voiture.dart // Classe fille 1 │ └── moto.dart // Classe fille 2 ├── services/ │ └── garage_service.dart // Classe de gestion └── main.dart // Point d'entrée
      • Instructions pas à pas
      • models/vehicule.dart
        • Déclarez une classe Vehicule avec 3 propriétés immuables :
            marque (String) annee (int) kilometrage (double)
        • Implémentez un constructeur principal.
        • Ajoutez une méthode afficherInfos() qui affiche : "[Véhicule] Marque: , Année: , Km: "
        • Surchargez la méthode toString() pour retourner une représentation lisible de l'objet.
    2. models/voiture.dart
      • Déclarez une classe Voiture qui hérite de Vehicule.
      • Ajoutez la propriété supplémentaire : nombrePortes (int).
      • Implémentez le constructeur en transmettant les paramètres communs au parent.
      • Surchargez @override afficherInfos() pour appeler la version parente, puis afficher :"Nombre de portes: "
      • Adaptez toString() pour inclure le nombre de portes.
    3. models/moto.dart
      • Déclarez une classe Moto qui hérite de Vehicule.
      • Ajoutez la propriété supplémentaire : cylindree (int, en cm³).
      • Implémentez le constructeur en transmettant les paramètres communs au parent.
      • Surchargez @override afficherInfos() pour appeler la version parente, puis afficher :"Cylindrée: cm³"
      • Adaptez toString() en conséquence.
    4. services/garage_service.dart
    5. Déclarez une classe GarageService avec : nomGarage (String)
    6. Une liste privée _vehicules de type List
    7. Implémentez les méthodes suivantes :
      • ajouterVehicule(Vehicule vehicule) : ajoute le véhicule à la liste et affiche un message de confirmation.
      • faireControleTechnique() : parcourt tous les véhicules, appelle afficherInfos() sur chacun, et utilise l'opérateur is pour afficher un message spécifique si l'objet est une Voiture ou une Moto.
      • listerVehicules() : affiche chaque véhicule en utilisant sa méthode toString().
      • Un getter vehicules qui retourne une version immuable de la liste.
    8. main.dart
      • Importez correctement les fichiers nécessaires.
      • Dans void main() :
        • Instanciez au moins 2 voitures et 2 motos avec des données différentes.
        • Créez une instance de GarageService.
        • Ajoutez tous les véhicules au service.
        • Appelez faireControleTechnique().
        • Appelez listerVehicules().
        • Faites un print() direct sur un objet pour vérifier que toString() fonctionne.
    9. Act02
      • Objectifs du TD
        • Ce TD à pour but de présenter le concept d’héritage en Dart. Il présente également un exercice sur la manipulation de tableau (via un exercice de tri).
      • Rappel : Héritage
        • Un cas concret: La classe Voiture représente toutes sortes de voitures possibles. On pourrait définir un camion comme une voiture très longue, très haute ? Mais un camion a des spécificités visà-vis des voitures : remorque,... On pourrait créer une classe Camion qui ressemble à la classe Voiture. Mais on ne veut pas réécrire tout ce qu’elles ont en commun.
        • La solution : La classe Vehicule contient tout ce qu’il y a de commun à Camion et Voiture. Camion par exemple ne contient que ce qu’il y a de spécifique aux camions.
        • L’héritage des classes Dart
        1. 01 Exercice: 01

          • Supposons que l’on dispose de la classe Date suivante :
              class Date {
                int jour, mois, annee;
              
                // Constructeur de la classe Date
                Date(int j, int m, int a) {
                  jour = j;
                  mois = m;
                  annee = a;
                }
              
                // Méthode pour définir une nouvelle date
                void setDate(int j, int m, int a) {
                  jour = j;
                  mois = m;
                  annee = a;
                }
              
                // Méthode pour obtenir une représentation sous forme de chaîne de la date
                @override
                String toString() {
                  return '$jour/$mois/$annee';
                }
              }
              
          • On veut définir une classe DateEvenement qui associe à une date donnée un événement qui la caractérise. La solution triviale serait de définir la classe DateEvenement en redéfinissant cette classe en prenant exemple sur la classe Date et en ajoutant les nouvelles fonctionnalités que l'on juge utiles.
          • Ecrire DateEvenement en utilisant l’héritage.
          • Solution
              // Classe DateEvenement héritant de la classe Date
              class DateEvenement extends Date {
                String event;
              
                // Constructeur de la classe DateEvenement
                DateEvenement(int j, int m, int a, String e) : super(j, m, a) {
                  event = e;
                }
              
                // Méthode pour définir une nouvelle date et un nouvel événement
                void setDateEvenement(int j, int m, int a, String e) {
                  super.setDate(j, m, a);
                  event = e;
                }
              
                // Méthode pour obtenir une représentation sous forme de chaîne de la date et de l'événement
                @override
                String toString() {
                  return '${super.toString()}->$event';
                }
              }
        2. 02 Exercice: 02

          • Ecrire une classe Animal qui dispose d’un attribut entier nbpattes. Cette classe dispose des méthodes suivantes :
            • Le constructeur, qui prend en argument un entier (le nombre de pattes),
            • String toString() qui renvoie une chaîne de caractères contenant le nombre de pattes de l’animal affiche() qui affiche le nombre de pattes de l’animal.
          • Ecrire une classe Autruche qui hérite de Animal.
          • Ecrire une classe Lapin qui hérite de Animal.
          • Ecrire une classe Main dans laquelle la méthode main() crée un lapin et une autruche.
            Solution
              class Animal {
                int nbPattes;
              
                // Constructeur de la classe Animal
                Animal(this.nbPattes);
              
                // Méthode toString pour afficher le nombre de pattes
                @override
                String toString() {
                  return 'Nombre de pattes : $nbPattes';
                }
              
                // Méthode affiche() pour afficher le nombre de pattes
                void affiche() {
                  print('Nombre de pattes : $nbPattes');
                }
              }
              
              // Classe Autruche héritant de la classe Animal
              class Autruche extends Animal {
                // Constructeur de la classe Autruche
                Autruche(int nbPattes) : super(nbPattes);
              }
              
              // Classe Lapin héritant de la classe Animal
              class Lapin extends Animal {
                // Constructeur de la classe Lapin
                Lapin(int nbPattes) : super(nbPattes);
              }
              
              // Classe Main
              void main() {
                // Création d'une autruche avec 2 pattes
                Autruche autruche = Autruche(2);
              
                // Création d'un lapin avec 4 pattes
                Lapin lapin = Lapin(4);
              
                // Affichage des informations sur l'autruche
                print('Informations sur l\'autruche:');
                print(autruche.toString());
                autruche.affiche();
              
                // Affichage des informations sur le lapin
                print('\nInformations sur le lapin:');
                print(lapin.toString());
                lapin.affiche();
              }
              
        3. 03 Exercice: 03

          • Soit le programme Dart suivant :
              import 'dart:math';
              
              class Shape {
                double x, y;
              
                Shape() {
                  x = 0;
                  y = 0;
                }
              
                Shape.withCoordinates(double x, double y) {
                  this.x = x;
                  this.y = y;
                }
              
                String toString() {
                  return "Position : ($x, $y)";
                }
              }
              
              class Circle extends Shape {
                static const double PI = 3.141592564;
                double radius;
              
                Circle() : radius = 0;
              
                Circle.withCoordinatesAndRadius(double x, double y, double r) : super.withCoordinates(x, y) {
                  radius = r;
                }
              
                @override
                String toString() {
                  return super.toString() + " Rayon : $radius";
                }
              }
              
              void main() {
                Circle c1, c2;
              
                c1 = Circle.withCoordinatesAndRadius(1, 1, 3);
                c2 = Circle();
              
                print('${c1.toString()}\n${c2.toString()}');
              }
              
            a) De quelles variables d’instance de shape hérite la classe Circle? b) La variable radius étant déclarée private, on ne peut la modifier de l’extérieur de la classe. Ajouter une méthode setRadius pour pouvoir le fixer et getRadius pour le lire. c) Ajoutez une variable surface à la classe Circle. Modifiez les méthodes setRadius et toString. Ajoutez les méthodes d’accès à ce champ.
              Solution
              • a) La classe Circle hérite des variables d'instance x et y de la classe Shape. En Dart, l'héritage se fait implicitement lorsque vous étendez une classe, et les membres non privés de la classe parente sont accessibles dans la classe dérivée. Ainsi, Circle hérite des variables x et y de la classe Shape.
              • b) Pour ajouter une méthode setRadius permettant de fixer la valeur du rayon et getRadius pour lire la valeur du rayon dans la classe Circle, vous pouvez effectuer les modifications suivantes :
              • class Circle extends Shape {
                  static const double PI = 3.141592564;
                  double _radius; // La variable radius est maintenant privée et commence par _
                
                  Circle() : _radius = 0;
                
                  Circle.withCoordinatesAndRadius(double x, double y, double r) : super.withCoordinates(x, y) {
                    _radius = r;
                  }
                
                  double getRadius() {
                    return _radius;
                  }
                
                  void setRadius(double radius) {
                    _radius = radius;
                  }
                
                  @override
                  String toString() {
                    return super.toString() + " Rayon : $_radius";
                  }
                }
              • Dans ce code, _radius est déclaré comme une variable privée en utilisant _ au début du nom. Les méthodes setRadius et getRadius sont ajoutées pour modifier et lire la valeur du rayon respectivement.
              • c) Pour ajouter une variable surface à la classe Circle et mettre à jour les méthodes setRadius et toString, vous pouvez procéder ainsi :
              • class Circle extends Shape {
                  static const double PI = 3.141592564;
                  double _radius;
                  double _surface; // Nouvelle variable surface
                
                  Circle() : _radius = 0, _surface = 0;
                
                  Circle.withCoordinatesAndRadius(double x, double y, double r) : super.withCoordinates(x, y) {
                    _radius = r;
                    _surface = calculateSurface();
                  }
                
                  double getRadius() {
                    return _radius;
                  }
                
                  void setRadius(double radius) {
                    _radius = radius;
                    _surface = calculateSurface(); // Mettre à jour la surface après modification du rayon
                  }
                
                  double getSurface() {
                    return _surface;
                  }
                
                  double calculateSurface() {
                    return PI * _radius * _radius;
                  }
                
                  @override
                  String toString() {
                    return super.toString() + " Rayon : $_radius, Surface : $_surface";
                  }
                }
              • Dans ce code, la variable surface est ajoutée, et les méthodes getSurface et calculateSurface sont introduites pour accéder à la surface et la calculer respectivement. La méthode setRadius est modifiée pour mettre à jour la surface après la modification du rayon.
            • 04 Exercice: 04

              • Ecrivez une classe Bâtiment avec les attributs suivants:
                • adresse
              • La classe Bâtiment doit disposer des constructeurs suivants:
                • Batiment(),
                • Batiment (adresse).
              • La classe Bâtiment doit contenir des accesseurs et mutateurs (ou propriétés) pour les différents attributs. La classe Bâtiment doit contenir une méthode ToString () donnant une représentation du Bâtiment.
              • Ecrivez une classe Maison héritant de Bâtiment avec les attributs suivants:
                • NbPieces: Le nombre de pièces de la maison.
              • La classe Maison doit disposer des constructeurs suivants:
                • Maison(),
                • Maison(adresse, nbPieces).
              • La classe Maison doit contenir des accesseurs et mutateurs (ou des propriétés) pour les différents attributs. La classe Maison doit contenir une méthode ToString () donnant une représentation de la Maison.
              • Ecrivez aussi un programme afin de tester ces deux classes .
              Solution
                class Batiment {
                  String adresse;
                
                  // Constructeur par défaut
                  Batiment() {
                    adresse = "Adresse par défaut";
                  }
                
                  // Constructeur avec adresse
                  Batiment.withAdresse(this.adresse);
                
                  // Accesseurs et mutateurs
                  String getAdresse() {
                    return adresse;
                  }
                
                  void setAdresse(String nouvelleAdresse) {
                    adresse = nouvelleAdresse;
                  }
                
                  // Méthode ToString
                  @override
                  String toString() {
                    return 'Batiment{adresse: $adresse}';
                  }
                }
                
                class Maison extends Batiment {
                  int nbPieces;
                
                  // Constructeur par défaut
                  Maison() : super() {
                    nbPieces = 0;
                  }
                
                  // Constructeur avec adresse et nombre de pièces
                  Maison.withAdresseNbPieces(String adresse, this.nbPieces) : super.withAdresse(adresse);
                
                  // Accesseurs et mutateurs pour nbPieces
                  int getNbPieces() {
                    return nbPieces;
                  }
                
                  void setNbPieces(int nouveauNbPieces) {
                    nbPieces = nouveauNbPieces;
                  }
                
                  // Méthode ToString
                  @override
                  String toString() {
                    return 'Maison{adresse: $adresse, nbPieces: $nbPieces}';
                  }
                }
                
                void main() {
                  // Test des classes Batiment et Maison
                  Batiment batiment1 = Batiment();
                  print(batiment1);
                
                  Batiment batiment2 = Batiment.withAdresse("123 Rue de la Test");
                  print(batiment2);
                
                  Maison maison1 = Maison();
                  print(maison1);
                
                  Maison maison2 = Maison.withAdresseNbPieces("456 Avenue du Test", 5);
                  print(maison2);
                
                  // Test des accesseurs et mutateurs
                  batiment1.setAdresse("789 Boulevard de l'Exemple");
                  print(batiment1.getAdresse());
                
                  maison1.setNbPieces(3);
                  print(maison1.getNbPieces());
                
                
                
                }
        4. 05 Exercice: 05 Système de Gestion des Employés

          • Une entreprise souhaite automatiser la gestion de son personnel. Tous les employés partagent des informations communes (identité, salaire, ancienneté), mais leurs métiers imposent des données et des règles de calcul différentes. Vous devez concevoir une architecture orientée objet évolutive, typée et facilement testable.
              lib/ ├── models/ │ ├── employe.dart │ ├── developpeur.dart │ ├── designer.dart │ └── manager.dart ├── services/ │ └── rh_service.dart └── main.dart
          • Consignes par fichier
              1️⃣ models/employe.dart
                Classe de base Employe avec les propriétés immuables : nom (String), prenom (String) salaire (double), anciennete (int en années) Constructeur principal Méthode void afficherFiche() affichant : "[Employé] | Salaire: € | Ancienneté: ans" Méthode double calculerBonus() retournant un bonus de base (ex: salaire * 0.05) @override String toString() retournant une représentation compacte de l'objet
              2️⃣ models/developpeur.dart
                Hérite de Employe Propriétés supplémentaires : langages (List), niveau (String : "Junior", "Mid", "Senior") Constructeur avec super parameters @override afficherFiche() : appelle super, puis affiche "Langages: [...]" et "Niveau: ..." @override calculerBonus() : Senior → salaire * 0.12 Mid → salaire * 0.08 Junior → salaire * 0.05
              3️⃣ models/designer.dart
                Hérite de Employe Propriétés supplémentaires : outils (List), specialite (String) Constructeur avec super @override afficherFiche() : ajoute "Outils: [...]" et "Spécialité: ..." @override calculerBonus() : retourne salaire * 0.07 + 200 (prime forfaitaire)
              4️⃣ models/manager.dart
                Hérite de Employe Propriétés supplémentaires : tailleEquipe (int), budgetDepartement (double) Constructeur avec super @override afficherFiche() : ajoute "Équipe: ... personnes" et "Budget: ...€" @override calculerBonus() : retourne salaire * 0.15 + (tailleEquipe * 50)
              5️⃣ services/rh_service.dart
                Classe RhService avec : nomEntreprise (String) _employes (List privée, initialisée vide) Méthodes : ajouterEmploye(Employe e) : ajout + message de confirmation listerEmployes() : affiche chaque employé via toString() distribuerBonus() : parcourt la liste, appelle afficherFiche() et calculerBonus(), affiche "Bonus versé : €" trierParSalaireCroissant() : trie la liste interne genererStatistiques() : utilise is pour compter le nombre de développeurs, designers et managers, et affiche un récapitulatif Getter employes retournant List.unmodifiable(_employes)
              6️⃣ main.dart
                Importez correctement les fichiers models/ et services/ Dans void main() : Instanciez au moins 2 employés de chaque type avec des données variées et cohérentes Créez une instance de RhService Ajoutez tous les employés au service Appelez listerEmployes() Appelez distribuerBonus() Triez la liste par salaire croissant, puis réaffichez l'inventaire Appelez genererStatistiques() Testez explicitement toString() avec un print() direct sur un objet