POLYMORPHISME de méthodes
 
Plan de ce chapitre:

Introduction
Vocabulaire et concepts
Surcharge dans la même classe
Surcharge statique
Surcharge dynamique
 

Polymorphisme (autres chapitres) :

d'objet

par héritage de classes abstraites

par implémentation d'interfaces

 

Introduction

Lorsqu'une classe enfant hérite d'une classe mère, des méthodes supplémentaires nouvelles peuvent être implémentées dans la classe enfant mais aussi des méthodes des parents redéfinies pour obtenir des implémentations différentes.

Une classe dérivée hérite de tous les membres de sa classe parent ; c'est-à-dire que tous les membres du parent sont disponibles pour l'enfant, rappelons qu'une méthode est un membre qualifiant un comportement d'un objet de la classe. En POO on distingue deux catégories de méthodes selon les besoins des applications et du polymorphisme : les méthodes statiques et les méthodes dynamiques.
 

Vocabulaire et concepts :
 
  • L'action qui consiste à donner le même nom à plusieurs méthodes dans la même classe ou d'une classe parent à une classe enfant, se dénomme d'une manière générale la surcharge de nom de méthode (avec ou non la même signature).

  •  
  • Le vocabulaire n'étant pas stabilisé selon les auteurs (surcharge, redéfinition, substitution,...) nous employerons les mots redéfinition, surcharge ou substitution dans le même sens, en précisant lorsque cela s'avérera nécessaire de quel genre de laison il s'agit.
Les actions des méthodes héritées du parent peuvent être modifiés par l'enfant de deux manières, selon le type de liaison du code utilisé pour la méthode (la liaison statique ou précoce ou bien la liaison dynamique ou retardée). 
Les deux modes de laison du code d'une méthode

La liaison statique ou précoce (early-binding) :

  • Lorsqu'une méthode à liaison statique est invoquée dans le corps d'un programme, le compilateur établit immédiatement dans le code appelant l'adresse précise et connue du code de la méthode à invoquer. Lors de l'exécution c'est donc toujours le même code invoqué.


La liaison dynamique ou retardée (lazy-binding) :

  • Lorsqu'une méthode à liaison dynamique est invoquée dans le corps d'un programme, le compilateur n'établit pas immédiatement dans le code appelant l'adresse de la méthode à invoquer. Le compilateur met en place un mécanisme de reférence (vide à la compilation) qui, lors de l'exécution, désignera (pointera vers) le code que l'on voudra invoquer; on pourra donc invoquer des codes différents.

Remonter



Surcharge dans la même classe :

Dans une classe donnée, plusieurs méthodes peuvent avoir le même nom, mais les signatures des méthodes ainsi surchargées doivent obligatoirement être différentes et peuvent éventuellement avoir des niveaux de visibilité différents.

Soit par exemple, la classe ClasseA ci-dessous, ayant 3 méthodes de même nom P, elles sont surchargées dans la classe selon 3 signatures différents :
Classe A

  public methode P(x,y);
  privé methode P(a,b,c);
  protégé methode P( );

finClasse A

La première surcharge de P dispose de 2 paramètres, la seconde de 3 paramètres, la dernière enfin n'a pas de paramètres. C'est le compilateur du langage qui devra faire le choix pour sélectionner le code de la bonne méthode à utiliser. Pour indiquer ce genre de surcharge, en Delphi il faut utiliser un qualificateur particulier dénoté overload.

Syntaxe de l'exemple en Delphi, en Java et en C# :
 
Delphi Java - C#
ClasseA = class
  public
      procedure P(x,y : integer);overload;
  private
      procedure P(a,b,c : string); overload;
  protected
      procedure P;overload;
end;
class ClasseA  {

  public void P( int x,y ){ }

  private void P( String a,b,c ){ }

  protected void P( ){ }
}

Utilisation pratique : permettre à une méthode d'accepter plusieurs types de paramètres en conservant le même nom, comme dans le cas d'opérateur arithmétique travaillant sur les entiers, les réels,...

Exemple de code Delphi :

ClasseA = class
  public
      procedure P(x,y : integer);overload;
      procedure P(a,b,c : string);overload;
      procedure P;overload;
end;

var Obj:ClasseA;
.....
Obj := ClasseA.create;
Obj.P( 10, 5 );
Obj.P( 'abc', 'ef', 'ghi' );
Obj.P;
 

Remonter



Surcharge statique dans une classe dérivée :

D'une manière générale, Delphi et C# disposent par défaut de la notion de méthode statique, Java n'en dispose pas sauf dans le cas des méthodes de classes. Dans l'exemple ci-dessous en Delphi et en C#, les trois méthodes P,Q et R sont à liaison statique dans leur déclaration par défaut sans utiliser de qualificateur spécial.
 
Delphi C#
ClasseA = class
  public
      procedure P(x,y : integer);
  private
      procedure Q(a,b,c : string);
  protected
      procedure R;
end;
class ClasseA  {

  public void P( int x,y ){ }

  private void Q( String a,b,c ){ }

  protected void R( ){ }
}

Une classe dérivée peut masquer une méthode à liaison statique héritée en définissant une nouvelle méthode avec le même nom.
 
Si vous déclarez dans une classe dérivée, une méthode ayant le même nom qu'une méthode à liaison statique d'une classe ancêtre, la nouvelle méthode remplace simplement la méthode héritée dans la classe dérivée.

Dans ce cas nous employerons aussi le mot de masquage qui semble être utilisé par beaucoup d'auteurs pour dénommer ce remplacement, car il correspond bien à l'idée d'un masquage "local" dans la classe fille du code de la méthode de la classe parent par le code de la méthode fille.

Ci-dessous un exemple de hiérarchie de classes et de masquages successifs licites de méthodes à liaison statiques dans certaines classes dérivées avec ou sans modification de visibilité :
 
Classe A

  public statique methode P;
  privé statique methode Q;
  protégé statique methode R;

finClasse A

Classe B hérite de Classe A

  public statique methode P;
  privé statique methode Q;
  protégé statique methode R;

finClasse B

Classe C hérite de Classe B

  protégé statique methode P;
  privé statique methode Q;
 

finClasse C

Classe D hérite de Classe C

  public statique methode P;
 
 

finClasse D

Classe E hérite de Classe D

  protégé statique methode P;
 
 

finClasse E

Classe F hérite de Classe E

  privé statique methode P;
  public statique methode R;
 

finClasse F

Dans le code d'implémentation de la Classe F :
La méthode P utilisée est celle qui définie dans la Classe F et elle masque la méthode P de la Classe E.
La méthode Q utilisée est celle qui définie dans la Classe C.
La méthode R utilisée est celle qui définie dans la Classe F et elle masque la méthode R de la Classe B

Soit en Delphi l'écriture des classes ClasseA et ClasseB de la hiérarchie ci-haut :
Delphi Explications
ClasseA = class
  public
      procedure P(x,y : integer);
  private
      procedure Q(a,b,c : string);
  protected
      procedure R;
end;

ClasseB = class ( ClasseA )
  public
      procedure P(u : char);
  private
      procedure Q(a,b,c : string);
  protected
      procedure R(x,y : real);
end;

Dans la classe ClasseB :

La méthode procedure P(u : char) surcharge statiquement (masque) avec une autre signature, la méthode héritée de sa classe parent procedure P(x,y : integer).

La méthode procedure Q(a,b,c : string) surcharge statiquement (masque) avec la même signature, la méthode héritée de sa classe parent procedure Q(a,b,c : string).

La méthode procedure R(x,y : real) surcharge statiquement (masque) avec une autre signature, la méthode héritée de sa classe parent procedure R.

Utilisation pratique : Possibilité notamment de définir un nouveau comportement lié à la classe descendante et éventuellement de changer le niveau de visibilité de la méthode.

Exemple de code Delphi :

ClasseA = class
  public
      procedure P(x,y : integer);
      procedure Q(a,b,c : string);
      procedure R;
end;

ClasseB = class ( ClasseA )
  public
      procedureP(u : char);
      procedure Q(a,b,c : string);
      procedure R(x,y : real);
end;
........

.........

var ObjA:ClasseA;
       ObjB:ClasseB;
.....
ObjA := ClasseA.create;
ObjA.P( 10, 5 );
ObjA.Q( 'abc', 'ef', 'ghi' );
ObjA.R;
.........
ObjB := ClasseB.create;
ObjB.P( 'g' );
ObjB.Q( 'abc', 'ef', 'ghi' );
ObjB.R( 1.2, -5.36 );
 

Remonter



Surcharge dynamique dans une classe dérivée :
 
Un type dérivé peut redéfinir (surcharger dynamiquement) une méthode à liaison dynamique héritée. On  appelle aussi virtuelle une telle méthode à liaison dynamique, nous utiliserons donc souvent ce raccourci de notation pour désigner une méthode surchargeable dynamiquement.
 
L'action de redéfinition fournit une nouvelle définition de la méthode qui sera appelée en fonction du type de l'objet au moment de l'exécution et non du type de la variable de reférence connue au moment de la compilation

Ci-dessous un exemple de hiérarchie de classes et de surcharge dynamique successifs fictifs de méthodes à liaison dynamique dans certaines classes dérivées, pour les modifications de visibilité il faut étudier le manuel de chaque langage :
 
Classe A

  public dynamique methode P;
  privé dynamique methode Q;
  protégé dynamique methode R;

finClasse A

Classe B hérite de Classe A

  public dynamique methode P;
  privé dynamique methode Q;
  protégé dynamique methode R;

finClasse B

Classe C hérite de Classe B

  protégé dynamique methode P;
  privé dynamique methode Q;
 

finClasse C

Classe D hérite de Classe C

  public dynamique methode P;
 

finClasse D

Classe E hérite de Classe D

  protégé dynamique methode P;
 

finClasse E

Classe F hérite de Classe E

  privé dynamique methode P;
  public dynamique methode R;

finClasse F

Remarque pratique :
Une méthode redéfinissant une méthode virtuelle peut selon les langages changer le niveau de visibilité ( il est conseillé de laisser la nouvelle méthode redéfinie au moins aussi visible que la méthode virtuelle parent).

Utilisation pratique : Possibilité de conserver le même nom de méthode dans une hiérarchie de classe, afin de ne pas "surcharger" le cerveau de l'utilisateur. Les comportement seront différents selon le type d'objet utilisé. par exemple l'action de Démarrer dans une hiérarchie de véhicules :
 
Nous voyons bien que sémantiquement parlant on peut dire qu'une voiture démarre, qu'un voilier démarre, qu'un croiseur démarre, toutefois les actions internes permettant le comportement démarrage ne sont pas les mêmes.

Démarrer dans la classe voiture : tourner la clef de contact, engager une vitesse,...

Démarrer dans la classe voilier : hisser les voiles, dégager la barre,...

Démarrer dans la classe croiseur : lancer les moteurs, modifier la barre,...

Pour indiquer au compilateur Delphi qu'une méthode est à liaison dynamique nous utiliserons un qualificateur noté virtual (il en existe un autre cf. chapitre 7.3 Surcharge statique et dynamique).

Soit en Delphi l'écriture de l'exemple de la hiérarchie précédente :
Delphi
Vehicule = class
  public
      procedure Demarrer; virtual;
end;

Terrestre = class ( Vehicule )
  .....
end;

Voiture = class ( Terrestre )
  public
      procedure Demarrer; override;
end;

Marin = class ( Vehicule )
  .....
end;

Voilier = class ( Marin )
  public
      procedure Demarrer; override;
end;

Croiseur = class ( Marin )
  public
      procedure Demarrer; override;
end;

Exemple de code Delphi :

Vehicule = class
  public
      procedure Demarrer; virtual; (1)
end;

Voiture = class ( Terrestre )
  public
      procedure Demarrer; override; (2)
end;
........
var Vehic1 : Vehicule;
       Auto1, Auto2 : Voiture;

 

.....

Vehic1 := Vehicule.Create;
Auto1 := Voiture.Create;
Auto2 := Voiture.Create;

Vehic1.Demarrer;
Auto1.Demarrer;
Auto2.Demarrer;
Vehic1 := Auto1;
Vehic1.Demarrer;
.........
Vehic1 := Voiture.Create;
Vehic1.Demarrer;
 

Illustrons l'exemple précédent avec une image de la partie de code généré sur la méthode Demarrer et ensuite l'exécution de ce code sur des objets effectifs. Nous avons numéroté (1) et (2) les deux codes d'implantation de la méthode Demarrer dans la classe parent et dans la classe enfant :
 

Le code engendré contient des reférences vides

Lors de l'exécution les reférences pointent vers le code de la méthode du type effectif de l'objet.