Skip to content

Latest commit

 

History

History
775 lines (625 loc) · 17.9 KB

File metadata and controls

775 lines (625 loc) · 17.9 KB

🔝 Retour au Sommaire

3.7.3 Héritage et polymorphisme en Object Pascal

Introduction

L'héritage et le polymorphisme sont deux concepts fondamentaux de la programmation orientée objet. Ils permettent de créer des hiérarchies de classes, de réutiliser du code et de créer des programmes plus flexibles et maintenables.

L'héritage

Qu'est-ce que l'héritage ?

L'héritage est un mécanisme qui permet à une classe (appelée classe dérivée ou classe fille) de récupérer automatiquement les propriétés et méthodes d'une autre classe (appelée classe de base ou classe parent).

Analogie du monde réel

Imaginez la classification des véhicules :

  • Tous les véhicules ont des roues, un moteur, peuvent démarrer et s'arrêter
  • Une voiture est un véhicule (elle hérite de ces caractéristiques)
  • Un camion est aussi un véhicule (il hérite aussi de ces caractéristiques)
  • Mais une voiture et un camion ont aussi leurs propres spécificités

Syntaxe de l'héritage

type
  // Classe de base
  TVehicule = class
  private
    FMarque: string;
    FVitesse: Integer;
  public
    procedure Demarrer;
    procedure Arreter;
    property Marque: string read FMarque write FMarque;
    property Vitesse: Integer read FVitesse;
  end;

  // Classe dérivée
  TVoiture = class(TVehicule)  // TVoiture hérite de TVehicule
  private
    FNombrePortes: Integer;
  public
    procedure Klaxonner;
    property NombrePortes: Integer read FNombrePortes write FNombrePortes;
  end;

La syntaxe class(TVehicule) indique que TVoiture hérite de TVehicule.

Implémentation

procedure TVehicule.Demarrer;  
begin  
  ShowMessage('Le véhicule ' + FMarque + ' démarre');
  FVitesse := 0;
end;

procedure TVehicule.Arreter;  
begin  
  FVitesse := 0;
  ShowMessage('Le véhicule s''arrête');
end;

procedure TVoiture.Klaxonner;  
begin  
  ShowMessage('Bip bip !');
end;

Utilisation de l'héritage

var
  MaVoiture: TVoiture;
begin
  MaVoiture := TVoiture.Create;
  try
    // Propriétés héritées de TVehicule
    MaVoiture.Marque := 'Peugeot';

    // Propriété propre à TVoiture
    MaVoiture.NombrePortes := 5;

    // Méthodes héritées de TVehicule
    MaVoiture.Demarrer;
    MaVoiture.Arreter;

    // Méthode propre à TVoiture
    MaVoiture.Klaxonner;
  finally
    MaVoiture.Free;
  end;
end;

Avantages de l'héritage

  1. Réutilisation du code : pas besoin de réécrire le code commun
  2. Organisation logique : structure hiérarchique claire
  3. Maintenance facilitée : modifications dans la classe de base bénéficient à toutes les classes dérivées
  4. Extensibilité : facile d'ajouter de nouvelles classes dérivées

Hiérarchie d'héritage

On peut créer plusieurs niveaux d'héritage :

type
  // Niveau 1 : Classe de base
  TAnimal = class
  private
    FNom: string;
    FAge: Integer;
  public
    procedure SeNourrir;
    property Nom: string read FNom write FNom;
    property Age: Integer read FAge write FAge;
  end;

  // Niveau 2 : Classe dérivée de TAnimal
  TMammifere = class(TAnimal)
  private
    FTypePoil: string;
  public
    procedure Allaiter;
    property TypePoil: string read FTypePoil write FTypePoil;
  end;

  // Niveau 3 : Classe dérivée de TMammifere
  TChien = class(TMammifere)
  private
    FRace: string;
  public
    procedure Aboyer;
    property Race: string read FRace write FRace;
  end;

  // Niveau 3 : Autre classe dérivée de TMammifere
  TChat = class(TMammifere)
  public
    procedure Miauler;
  end;

Dans cet exemple :

  • TChien hérite de TMammifere qui hérite de TAnimal
  • TChien a accès à toutes les propriétés et méthodes de TMammifere et TAnimal
  • TChat et TChien partagent les caractéristiques communes de TMammifere

Le mot-clé inherited

Le mot-clé inherited permet d'appeler la méthode de la classe parent :

type
  TVehicule = class
  public
    constructor Create;
    procedure Demarrer; virtual;
  end;

  TVoiture = class(TVehicule)
  public
    constructor Create;
    procedure Demarrer; override;
  end;

constructor TVehicule.Create;  
begin  
  inherited Create;  // Appelle le constructeur de TObject
  ShowMessage('Création d''un véhicule');
end;

constructor TVoiture.Create;  
begin  
  inherited Create;  // Appelle le constructeur de TVehicule
  ShowMessage('Création d''une voiture');
end;

procedure TVehicule.Demarrer;  
begin  
  ShowMessage('Le véhicule démarre');
end;

procedure TVoiture.Demarrer;  
begin  
  inherited Demarrer;  // Appelle d'abord la méthode de TVehicule
  ShowMessage('Mise en route du système de la voiture');
end;

Le polymorphisme

Qu'est-ce que le polymorphisme ?

Le polymorphisme (du grec "plusieurs formes") est la capacité d'un objet à prendre plusieurs formes. En programmation, cela signifie qu'une variable de type classe parent peut contenir un objet de n'importe quelle classe dérivée.

Méthodes virtuelles

Pour activer le polymorphisme, on utilise les mots-clés virtual et override :

type
  TAnimal = class
  public
    procedure EmettreSon; virtual;  // Méthode virtuelle
  end;

  TChien = class(TAnimal)
  public
    procedure EmettreSon; override;  // Redéfinition
  end;

  TChat = class(TAnimal)
  public
    procedure EmettreSon; override;  // Redéfinition
  end;

  TVache = class(TAnimal)
  public
    procedure EmettreSon; override;  // Redéfinition
  end;

// Implémentation

procedure TAnimal.EmettreSon;  
begin  
  ShowMessage('L''animal fait un son');
end;

procedure TChien.EmettreSon;  
begin  
  ShowMessage('Wouf wouf !');
end;

procedure TChat.EmettreSon;  
begin  
  ShowMessage('Miaou !');
end;

procedure TVache.EmettreSon;  
begin  
  ShowMessage('Meuh !');
end;

Utilisation du polymorphisme

Voici la magie du polymorphisme :

procedure FaireCrier(UnAnimal: TAnimal);  
begin  
  // La méthode appelée dépend du type réel de l'objet
  UnAnimal.EmettreSon;
end;

var
  MonChien: TChien;
  MonChat: TChat;
  MaVache: TVache;
begin
  MonChien := TChien.Create;
  MonChat := TChat.Create;
  MaVache := TVache.Create;
  try
    // Même procédure, comportements différents !
    FaireCrier(MonChien);  // Affiche "Wouf wouf !"
    FaireCrier(MonChat);   // Affiche "Miaou !"
    FaireCrier(MaVache);   // Affiche "Meuh !"
  finally
    MonChien.Free;
    MonChat.Free;
    MaVache.Free;
  end;
end;

Exemple avec une liste d'animaux

Le polymorphisme brille vraiment quand on traite des collections :

var
  Animaux: TList<TAnimal>;
  Animal: TAnimal;
begin
  Animaux := TList<TAnimal>.Create;
  try
    // Ajouter différents types d'animaux
    Animaux.Add(TChien.Create);
    Animaux.Add(TChat.Create);
    Animaux.Add(TVache.Create);
    Animaux.Add(TChien.Create);

    // Faire crier tous les animaux
    for Animal in Animaux do
      Animal.EmettreSon;  // Chaque animal fait son propre son !

  finally
    // Libérer tous les animaux
    for Animal in Animaux do
      Animal.Free;
    Animaux.Free;
  end;
end;

Méthodes abstraites

Une méthode abstraite est une méthode qui DOIT être redéfinie dans les classes dérivées. Elle n'a pas d'implémentation dans la classe de base.

type
  TForme = class
  protected
    FCouleur: string;
  public
    function CalculerSurface: Double; virtual; abstract;
    function CalculerPerimetre: Double; virtual; abstract;
    procedure Dessiner; virtual; abstract;
    property Couleur: string read FCouleur write FCouleur;
  end;

  TRectangle = class(TForme)
  private
    FLargeur: Double;
    FHauteur: Double;
  public
    constructor Create(ALargeur, AHauteur: Double);
    function CalculerSurface: Double; override;
    function CalculerPerimetre: Double; override;
    procedure Dessiner; override;
    property Largeur: Double read FLargeur write FLargeur;
    property Hauteur: Double read FHauteur write FHauteur;
  end;

  TCercle = class(TForme)
  private
    FRayon: Double;
  public
    constructor Create(ARayon: Double);
    function CalculerSurface: Double; override;
    function CalculerPerimetre: Double; override;
    procedure Dessiner; override;
    property Rayon: Double read FRayon write FRayon;
  end;

// Implémentation TRectangle

constructor TRectangle.Create(ALargeur, AHauteur: Double);  
begin  
  inherited Create;
  FLargeur := ALargeur;
  FHauteur := AHauteur;
end;

function TRectangle.CalculerSurface: Double;  
begin  
  Result := FLargeur * FHauteur;
end;

function TRectangle.CalculerPerimetre: Double;  
begin  
  Result := 2 * (FLargeur + FHauteur);
end;

procedure TRectangle.Dessiner;  
begin  
  ShowMessage(Format('Rectangle %s : %.2f x %.2f', [FCouleur, FLargeur, FHauteur]));
end;

// Implémentation TCercle

constructor TCercle.Create(ARayon: Double);  
begin  
  inherited Create;
  FRayon := ARayon;
end;

function TCercle.CalculerSurface: Double;  
begin  
  Result := Pi * FRayon * FRayon;
end;

function TCercle.CalculerPerimetre: Double;  
begin  
  Result := 2 * Pi * FRayon;
end;

procedure TCercle.Dessiner;  
begin  
  ShowMessage(Format('Cercle %s de rayon %.2f', [FCouleur, FRayon]));
end;

Utilisation des classes abstraites

procedure AfficherInfosForme(Forme: TForme);  
begin  
  Forme.Dessiner;
  ShowMessage(Format('Surface : %.2f', [Forme.CalculerSurface]));
  ShowMessage(Format('Périmètre : %.2f', [Forme.CalculerPerimetre]));
end;

var
  Rectangle: TRectangle;
  Cercle: TCercle;
begin
  Rectangle := TRectangle.Create(5, 3);
  try
    Rectangle.Couleur := 'bleu';
    AfficherInfosForme(Rectangle);
  finally
    Rectangle.Free;
  end;

  Cercle := TCercle.Create(4);
  try
    Cercle.Couleur := 'rouge';
    AfficherInfosForme(Cercle);
  finally
    Cercle.Free;
  end;
end;

Différence entre virtual, override et reintroduce

Virtual

Déclare qu'une méthode peut être redéfinie dans les classes dérivées.

type
  TBase = class
    procedure MaMethode; virtual;
  end;

Override

Redéfinit une méthode virtuelle de la classe parent.

type
  TDerivee = class(TBase)
    procedure MaMethode; override;
  end;

Reintroduce

Masque une méthode du parent au lieu de la redéfinir (pas de polymorphisme).

type
  TDerivee = class(TBase)
    procedure MaMethode; reintroduce;  // Cache la méthode du parent
  end;

Attention : reintroduce brise le polymorphisme. Préférez toujours override quand c'est possible.

Le mot-clé sealed

Le mot-clé sealed empêche qu'une méthode soit redéfinie dans les classes dérivées :

type
  TBase = class
    procedure MaMethode; virtual;
  end;

  TDerivee = class(TBase)
    procedure MaMethode; override; sealed;  // Ne peut plus être redéfinie
  end;

  TNiveauSuivant = class(TDerivee)
    // Erreur de compilation si on essaie de redéfinir MaMethode
  end;

Exemple complet : Système de paiement

Voici un exemple complet illustrant héritage et polymorphisme :

type
  // Classe de base abstraite
  TMethodePaiement = class
  protected
    FMontant: Double;
  public
    constructor Create(AMontant: Double);
    function Traiter: Boolean; virtual; abstract;
    function ObtenirDescription: string; virtual; abstract;
    property Montant: Double read FMontant;
  end;

  // Classe dérivée : Carte de crédit
  TCarteCredit = class(TMethodePaiement)
  private
    FNumeroCarte: string;
    FNomTitulaire: string;
  public
    constructor Create(AMontant: Double; ANumero, ANom: string);
    function Traiter: Boolean; override;
    function ObtenirDescription: string; override;
  end;

  // Classe dérivée : PayPal
  TPayPal = class(TMethodePaiement)
  private
    FEmail: string;
  public
    constructor Create(AMontant: Double; AEmail: string);
    function Traiter: Boolean; override;
    function ObtenirDescription: string; override;
  end;

  // Classe dérivée : Virement bancaire
  TVirement = class(TMethodePaiement)
  private
    FIBAN: string;
  public
    constructor Create(AMontant: Double; AIBAN: string);
    function Traiter: Boolean; override;
    function ObtenirDescription: string; override;
  end;

// Implémentation TMethodePaiement

constructor TMethodePaiement.Create(AMontant: Double);  
begin  
  inherited Create;
  FMontant := AMontant;
end;

// Implémentation TCarteCredit

constructor TCarteCredit.Create(AMontant: Double; ANumero, ANom: string);  
begin  
  inherited Create(AMontant);
  FNumeroCarte := ANumero;
  FNomTitulaire := ANom;
end;

function TCarteCredit.Traiter: Boolean;  
begin  
  // Simulation du traitement
  ShowMessage('Traitement du paiement par carte de crédit...');
  Result := True;
end;

function TCarteCredit.ObtenirDescription: string;  
begin  
  Result := Format('Carte de crédit %s - %.2f €',
                   [FNomTitulaire, FMontant]);
end;

// Implémentation TPayPal

constructor TPayPal.Create(AMontant: Double; AEmail: string);  
begin  
  inherited Create(AMontant);
  FEmail := AEmail;
end;

function TPayPal.Traiter: Boolean;  
begin  
  ShowMessage('Traitement du paiement PayPal...');
  Result := True;
end;

function TPayPal.ObtenirDescription: string;  
begin  
  Result := Format('PayPal (%s) - %.2f €', [FEmail, FMontant]);
end;

// Implémentation TVirement

constructor TVirement.Create(AMontant: Double; AIBAN: string);  
begin  
  inherited Create(AMontant);
  FIBAN := AIBAN;
end;

function TVirement.Traiter: Boolean;  
begin  
  ShowMessage('Traitement du virement bancaire...');
  Result := True;
end;

function TVirement.ObtenirDescription: string;  
begin  
  Result := Format('Virement bancaire - %.2f €', [FMontant]);
end;

Utilisation du système de paiement

procedure EffectuerPaiement(Paiement: TMethodePaiement);  
begin  
  ShowMessage('Paiement : ' + Paiement.ObtenirDescription);

  if Paiement.Traiter then
    ShowMessage('Paiement réussi !')
  else
    ShowMessage('Échec du paiement');
end;

var
  Carte: TCarteCredit;
  PayPal: TPayPal;
  Virement: TVirement;
  Paiements: TList<TMethodePaiement>;
  Paiement: TMethodePaiement;
begin
  Paiements := TList<TMethodePaiement>.Create;
  try
    // Créer différents modes de paiement
    Paiements.Add(TCarteCredit.Create(150.50, '1234-5678-9012-3456', 'Jean Dupont'));
    Paiements.Add(TPayPal.Create(75.00, 'jean@example.com'));
    Paiements.Add(TVirement.Create(200.00, 'FR76 1234 5678 9012 3456 7890 123'));

    // Traiter tous les paiements (polymorphisme en action)
    for Paiement in Paiements do
      EffectuerPaiement(Paiement);

  finally
    // Libérer les objets
    for Paiement in Paiements do
      Paiement.Free;
    Paiements.Free;
  end;
end;

Type casting et vérification de type

L'opérateur is

Vérifie si un objet est d'un type spécifique :

var
  Animal: TAnimal;
  Chien: TChien;
begin
  Chien := TChien.Create;
  try
    Animal := Chien;  // Affectation polymorphe

    if Animal is TChien then
      ShowMessage('C''est un chien !');

    if Animal is TChat then
      ShowMessage('C''est un chat');  // Ne s'affichera pas

  finally
    Chien.Free;
  end;
end;

L'opérateur as

Convertit un objet vers un type spécifique (génère une exception si impossible) :

var
  Animal: TAnimal;
  Chien: TChien;
  MonChien: TChien;
begin
  Chien := TChien.Create;
  try
    Animal := Chien;

    // Conversion sûre
    if Animal is TChien then
    begin
      MonChien := Animal as TChien;
      MonChien.Aboyer;
    end;

  finally
    Chien.Free;
  end;
end;

Bonnes pratiques

  1. Utilisez virtual/override pour le polymorphisme

    // ✅ Bon - permet le polymorphisme
    procedure MaMethode; virtual;
    procedure MaMethode; override;
  2. Déclarez abstract les méthodes sans implémentation logique dans la classe de base

    function Calculer: Double; virtual; abstract;
  3. Utilisez inherited pour appeler la méthode du parent

    procedure MaMethode; override;
    begin
      inherited MaMethode;  // Appelle le parent
      // Code supplémentaire
    end;
  4. Évitez les hiérarchies trop profondes

    • Maximum 3-4 niveaux idéalement
    • Préférez la composition à l'héritage quand approprié
  5. Utilisez des noms de classe explicites

    TAnimal → TMammifere → TChien  // ✅ Clair
    TBase → TDerivee → TSuite      // ❌ Peu clair
  6. Vérifiez toujours le type avant le cast

    if Animal is TChien then
      (Animal as TChien).Aboyer;

Héritage vs Composition

Il est important de savoir quand utiliser l'héritage et quand utiliser la composition :

Utilisez l'héritage quand

  • Il y a une vraie relation "est un" (un chien est un animal)
  • Vous voulez du polymorphisme
  • La classe de base fournit du comportement commun

Utilisez la composition quand

  • Il y a une relation "a un" (une voiture a un moteur)
  • Vous voulez plus de flexibilité
  • L'héritage créerait une hiérarchie trop complexe

Résumé

  • L'héritage permet à une classe de récupérer les propriétés et méthodes d'une autre classe

    • Syntaxe : class(TClasseParent)
    • Mot-clé inherited pour appeler la méthode parent
  • Le polymorphisme permet à un objet de prendre plusieurs formes

    • Utilisez virtual dans la classe de base
    • Utilisez override dans les classes dérivées
    • Permet de traiter des objets différents de manière uniforme
  • Méthodes abstraites (abstract)

    • N'ont pas d'implémentation dans la classe de base
    • Doivent être implémentées dans les classes dérivées
  • Opérateurs de type

    • is : vérifie le type
    • as : convertit vers un type (cast)
  • Avantages

    • Réutilisation du code
    • Organisation logique
    • Flexibilité et extensibilité
    • Facilite la maintenance

L'héritage et le polymorphisme sont des outils puissants qui, utilisés correctement, permettent de créer des architectures logicielles élégantes et maintenables.

⏭️ Constructeurs et destructeurs