7.3. Surcharge statique et dynamique
(à partir de la doc. Delphi ÔBorland-Inprise )
Plan du chapitre:1. Principes généraux de la surcharge d'une méthode en Delphi
1.1 Répartition des méthodes en Delphi2. Différentes façon de surcharger une méthode en Delphi
1.2 Méthodes statiques en Delphi
1.3 Méthodes virtuelles en Delphi
1.4 Méthodes dynamiques en Delphi
2.1 Un exemple de surcharge de méthode
2.2 Réutilisation de méthodes statiques avec inherited
2.3 Réutilisation de méthodes dynamiques avec inherited
2.4 Redéfinition de procédures et de fonctions
2.5 Redéfinition de méthodes
2.6 Méthodes abstraites
1. Principes généraux de la surcharge d'une méthode en Delphi
Surcharge = la possibilité d'attacher plusieurs significations à un même nom qui apparaît dans un programme. Les opérations dans les TAD sont les candidats principaux à la surcharge. |
Ce n'est qu'une facilité syntaxique qui dispense le programmeur d'avoir à inventer différents noms pour différentes implémentations d'une opération.
La surcharge est une facilité offerte aux programmeurs clients : elle rend possible l'écriture d'un même code client en utilisant plusieurs implémentations d'une structure de données.
En terme de qualité
du logiciel, la surcharge apporte un progrès sur la réutilisabilité
en résolvant les deux problème suivants :
- Prendre en compte différents types d'éléments pour effectuer la même opération (additionner un entier, ou additionner un rationnel),
- Permet aux modules clients de demander l'exécution d'une opération sur une donnée sans avoir à connaître son implémentation.
Vous pouvez, utiliser
le nom d'une méthode se trouvant dans un objet ancêtre pour
la déclarer dans un objet descendant. C'est ce qu'on appelle surcharger
une méthode.
Le plus souvent, vous aurez besoin de surcharger une méthode existante si vous voulez que celle de l'objet descendant fasse la même chose que celle de l'objet ancêtre, mais différemment. Autrement dit, le code qui implémente les deux méthodes diffère.
Ce n'est que rarement
que vous aurez besoin de surcharger une méthode, à moins
de programmer uniquement en objet ou de créer de nouveaux composants.
Il est cependant utile de savoir que vous pouvez le faire sans recevoir
d'avertissement ni de message d'erreur du compilateur.
1.1 Répartition des
méthodes en Delphi
Le terme de répartition fait référence à la façon dont un programme détermine où il doit rechercher une méthode lorsqu'il rencontre un appel à cette méthode. Le code qui appelle une méthode ressemble à tout autre appel de procédure ou de fonction. Mais les classes ont des façons différentes de répartir les méthodes. |
Les trois modes de répartition
des méthodes peuvent être :
|
1.2 Méthodes statiques en Delphi
Toute méthode est considérée comme statique sauf si vous la déclarez autrement. Les méthodes statiques fonctionnent de la même façon que les procédures et les fonctions standard. Le compilateur détermine l'adresse exacte de la méthode et lie la méthode au moment de la compilation.
L'avantage principal des méthodes statiques est que leur répartition est très rapide. Comme le compilateur peut déterminer l'adresse exacte de la méthode, il la lie directement.
(Les méthodes virtuelles et dynamiques, au contraire, utilisent un moyen indirect pour récupérer l'adresse des méthodes à l'exécution, moyen qui nécessite plus de temps).
Une méthode statique ne change pas lorsqu'elle est transmise en héritage à une autre classe. Si vous déclarez une classe qui inclut une méthode statique, puis en dérivez une nouvelle classe, la classe dérivée partage exactement la même méthode située à la même adresse. Cela signifie qu'il est impossible de surcharger les méthodes statiques; une méthode statique fait toujours exactement la même chose, quelque soit la classe dans laquelle elle est appelée.
Si vous déclarez dans une classe dérivée une méthode ayant le même nom qu'une méthode statique de la classe ancêtre, la nouvelle méthode remplace simplement la méthode héritée dans la classe dérivée.
Dans le code suivant, le premier composant déclare deux méthodes statiques. Le deuxième déclare deux méthodes statiques ayant les mêmes noms : elles remplacent donc les méthodes héritées du premier composant.
type
MaClasse=class(Tedit)
Etat:string;
procedure
Un; //statique
procedure
Deux; //statique
procedure
Trois; //statique
end;
SousClasse=class(MaClasse)
procedure
Un ; //redéfinie
et statique, masque la méthode ancêtre
procedure
Deux(x:string;c:char) ; //redéfinie et
statique, masque la méthode ancêtre
procedure
Trois;virtual; //redéfinie
et surchargeable, masque la méthode ancêtre
end;
1.3 Méthodes virtuelles en Delphi
Les méthodes virtuelles utilisent un mécanisme de répartition plus complexe mais aussi plus souple que les méthodes statiques. Une méthode virtuelle peut être redéfinie dans les classes descendantes, mais peut toujours être appelée dans la classe ancêtre. L'adresse de la méthode virtuelle n'est pas déterminée lors de la compilation; en revanche, l'objet où la méthode est définie recherche cette adresse au moment de l'exécution.
Pour rendre virtuelle une méthode, ajoutez la directive virtual à la fin de la déclaration de la méthode. La directive virtual crée une entrée dans la VMT (virtual method table) de l'objet, qui contient les adresses de toutes les méthodes virtuelles d'un type d'objet.
Lorsque vous dérivez
un nouvelle classe à partir d'une autre, la nouvelle classe récupère
sa propre VMT, qui comprend toutes les entrées des VMT de ses ancêtres,
plus les ajouts correspondant aux méthodes virtuelles déclarées
dans la nouvelle classe.
Lors de la compilation, Delphi
crée pour chaque classe une Table des Méthodes Virtuelles
(TMV). Cette table contient pour chaque méthode déclarée
en virtual (outre la taille de l'objet lui-même) un pointeur
sur le code correspondant à la méthode.
Il n'existe donc qu'une TMV par objet. Elle est remplie lors de l'exécution du programme et non lors de l'édition de lien à la fin de la compilation (ce qui est le cas pour une méthode statique). C'est à l'exécution que l'adresse du code de la méthode est connue et donc stockée dans la TMV. C'est ainsi que Delphi effectue la liaison dynamique. En fait c'est le constructeur (mot clef constructor) de l'objet instancié qui lance, lors de l'exécution, entre autres fonctionnalités, le remplissage de la TMV de cet objet s'il y a lieu. La table des méthodes virtuelles, inclut toutes les méthodes virtuelles de l'objet, à la fois les méthodes héritées et les méthodes introduites. |
1.4 Méthodes dynamiques en Delphi
Les méthodes dynamiques
sont des méthodes virtuelles avec un mécanisme de répartition
légèrement différent.
Au lieu de créer
des entrées dans la table des méthodes virtuelles, les méthodes
dynamiques sont inscrites dans une liste séparée.
La liste des méthodes dynamiques contient des entrées pour les seules méthodes introduites ou surchargées par une classe particulière. Les méthodes dynamiques héritées sont réparties en recherchant dans la liste des méthodes dynamiques de chaque ancêtre, dans l'ordre inverse de l'héritage. |
Pour rendre dynamique
une méthode, ajoutez la directive
dynamic à la fin
de la déclaration de la méthode.
Comme les méthodes dynamiques ne disposent pas d'entrées dans la table des méthodes virtuelles de l'objet, elles réduisent la quantité de mémoire occupée par les objets. Mais la répartition des méthodes dynamiques est sensiblement moins rapide que la répartition des méthodes virtuelles standard. |
Conseil de Borland
Si une méthode est appelé fréquemment, ou si le temps d'exécution est un paramètre important, il vaut mieux déclarer une méthode virtuelle plutôt que dynamique. |
2.
Différentes façon de surcharger une méthode en Delphi
Surcharger une méthode signifie l'étendre ou la redéfinir, plutôt que la remplacer. Une classe descendante peut surcharger n'importe laquelle de ses méthodes virtuelles héritées.
Pour surcharger une méthode d'une classe descendante, ajoutez la directive override à la fin de la déclaration de la méthode.
Remarque :
La directive override est le seul moyen de surcharger une méthode virtuelle. Si une déclaration de méthode dans une classe descendante indique le même identificateur de méthode que celui d'une méthode reçue en héritage, mais sans la directive override, la nouvelle méthode masque la déclaration dont elle hérite, sans la surcharger. |
Surcharger avec la directive
override
une méthode provoque une erreur de compilation si :
2.1 Un exemple de surcharge de méthode
Le code suivant montre la déclaration de deux composants simples. Le premier déclare trois méthodes, chacune ayant un style de répartition différent. Le deuxième composant, dérivé du premier, remplace la méthode statique et surcharge les méthodes virtuelles.
type
MaClasse=class(Tedit)
Etat:string;
procedure
Un; //statique
procedure
Deux; //statique
procedure
Trois; //statique
procedure
Quatre; virtual; //méthode
virtuelle surchargeable
procedure
Cinq; dynamic; //méthode
virtuelle dynamique surchargeable
procedure
Clear; override; //surcharge de la
méthode clear des TEdit
constructor
create(x:Tcomponent); override ; //surcharge
le constructeur
end;
SousClasse=class(MaClasse)
procedure
Un ; //redéfinie
et statique
procedure
Deux(x:string;c:char) ; //redéfinie et
statique
procedure
Trois; virtual; //redéfinie
et surchargeable
procedure
Quatre; override; //surcharge
la méthode héritée
procedure
Clear; override; //surcharge
la méthode héritée
end;
DerniereClasse=class(SousClasse)
procedure
Trois; override; //surcharge la méthode
héritée
procedure
Quatre; override; //surcharge la méthode
héritée
procedure
Cinq; override;//surcharge la méthode
héritée
end;
2.2 Réutilisation de méthodes statiques avec inherited
A l'intérieur
d'un bloc de méthode, le mot réservé inherited
peut être utilisé pour accéder aux identificateurs
de composant, redéclarés ou surchargés.
Lorsqu'un identificateur est préfixé par inherited,
la recherche de l'identificateur débute par l'ancêtre immédiat
du type classe auquel appartient la méthode.
MaClasse=class ( Tedit ) publicEtat:string;end; |
SousClasse=class ( MaClasse ) publicprocedure Un ;end; |
implementation{$R *.DFM}
//////// LE CONSTRUCTOR //////////////////////////////////
constructor MaClasse.create(x:Tcomponent);
begin
inherited ; // on peut omettre le nom, car la déclaration est identique
{ inherited : pour montrer que bien que caché, on peut atteindre malgré tout le constructor create de la classe parent des Tedit, le override n'est utile que parce que le constructor a été déclaré dans la classe parent du Tedit avec le mot clef technique virtual qui implante une technique équivalente à celle des méthodes virtuelles du langage C++.
}color:=clyellow;end;
width:=200;///// Méthodes de la classe MaClasse ///////////////////////
procedure MaClasse.Un;
begintext:='MaClasse.Un';end;procedure MaClasse.Deux;
begintext:='MaClasse.Deux';end;/// Méthodes de la classe SousClasse /////////////////////////
procedure SousClasse.Un;
begininherited ; // on peut omettre le nom, car la déclaration est identiqueend;
text:=text+'/SousClasse.Un';procedure SousClasse.Deux(x:string;c:char);
begininherited Deux; // le nom est obligatoire, car les paramètres ne sont pas identiquesend;
text:=text+'/SousClasse.Deux/'+x+'/'+c;end.
2.3 Réutilisation de méthodes dynamiques avec inherited
Lors de la surcharge d’une méthode dynamique (dynamic ou virtual) le qualificateur inherited est utilisé d’une manière identique à celle du paragraphe précédent :
Toutefois en Delphi 3, il est obligatoire que l’en-tête de la méthode qui " surcharge " soit identique à l’en-tête de la méthode qui est surchargée (à partir de la version 4 les directives overload et reintroduice permettent de lever cette obligation, cf. § ci-dessous) :
MaClasse=class ( Tedit )
publicEtat:string;end;
procedure Un;
procedure Deux ; virtual ; // méthode surchargeable
constructor create(x:Tcomponent);SousClasse=class ( MaClasse )
publicprocedure Un ;end;
procedure Deux ; override ; // surcharge de la méthode ancêtre (en-tête identique)implementation
{$R *.DFM}
/// Méthodes de la classe MaClasse ///////////////////////
procedure MaClasse.Deux ;
begintext:='MaClasse.Deux';end;/// Méthodes de la classe SousClasse /////////////////////////
procedure SousClasse.Deux ;
begininherited ; // le nom n’ est pas obligatoireend;
text:=text+'/SousClasse.Deux/'+x+'/'+c;
2.4 Redéfinition de procédures et de fonctions
A partir de la version 4, il devient possible de redéclarer plusieurs fois une routine dans la même portée sous le même nom. C'est ce que l'on appelle la redéfinition (ou véritable surcharge indépendante de la signature).
Les routines redéfinies doivent être redéclarées avec la directive overload et doivent obligatoirement utiliser une liste de paramètres différente (signature différente). Soit, par exemple, les déclarations :
function Calcul(X, Y: Real): Real; overload;
beginResult := X + Y;end;function Calcul(X, Y: Integer): Integer; overload;
beginResult := X * Y;end;Ces déclarations créent deux fonctions appelées toutes les deux Calcul qui attendent des paramètres de types différents.
Lorsque vous appelez Calcul, le compilateur détermine la fonction à utiliser en examinant les paramètres effectivement transmis dans l'appel. Ainsi, Calcul(4.57, -12.89) appelle la première fonction Calcul car ses arguments sont des valeurs réelles.
Les routines redéfinies doivent pouvoir se distinguer par le nombre ou le type de leurs paramètres. les déclarations suivantes sont correctes :
function F1(X: string; Y: real): Real; overload;
...
function F1(X: real; Y: boolean): Real; overload;
...ou bien
function F1(Y: real): Real; overload;
...
function F1(X: real; Y: boolean): char; overload;
...
- Une méthode peut être redéclarée en utilisant la directive overload.
- Dans ce cas, si la méthode redéclarée a une signature de paramètres différente de celle de son ancêtre, elle redéfinit la méthode héritée sans la cacher.
- L'appel de la méthode dans une classe dérivée active l'implémentation qui correspond aux paramètres utilisés dans l'appel.
Si vous redéfinissez une méthode virtuelle, utilisez la directive reintroduce quand vous la redéclarez dans les classes dérivées. Par exemple :
type
Classe1 = class(TObject)procedure Exemple(I: real); overload;virtual;end;Classe2 = class(Classe1) procedure Exemple(x,y:integer); reintroduce; overload;end;...
UnObjet := Classe2.Create;
UnObjet.Exemple(12.456); // appelle Classe2.Exemple
UnObjet.Exemple(-3,84); // appelle Classe1.Exemple
L'implémentation d'une méthode surchargée doit répéter la liste des paramètres spécifiée dans la liste de paramètres de la déclaration de classe :
procedure Classe1.Exemple(I: real);
begin
...
end;procedure Classe2.Exemple(x,y:integer);
begin
...
end;
Une méthode abstraite est une méthode virtual ou dynamic dont l'implémentation n'est pas définie dans la déclaration de la classe où elle apparaît ; Son implémentation est déléguée à une classe dérivée. Les méthodes abstraites doivent être déclarées en spécifiant la directive abstract après virtual ou dynamic.
- En fait, une méthode abstraite définit une interface sans définir d'action et ne doit doc pas être implémentée.
- Elle joue le rôle d'un prototype d'interface pour les méthodes qui la surchargeront.
Une méthode est abstraite à partir du moment où une directive abstract est insérée dans sa déclaration. Une méthode ne peut être déclarée abstract que si elle est déclarée virtual ou dynamic.
procedure AbstraiTruc; virtual; abstract; function AbstraiChose(x:char):Tobject;dynamic;abstract;
La surcharge d'une méthode abstraite ressemble à celle d'une méthode virtuelle ou dynamique ordinaire, avec toutefois une légère différence dans l'implémentation de la méthode de surcharge :
il n'est pas possible de faire appel à une méthode abstraite inherited. Vous ne pouvez appeler une méthode abstraite que dans une classe ou une instance de classe dans laquelle la méthode a été surchargée.
Exemple :
type
Nombre=class
Valeur_i:integer;
Valeur_r:real;
procedure afficher; virtual; abstract;
procedure add(x:Nombre); virtual; abstract;
end;reel=class(Nombre)
procedure afficher;override;
procedure add(x:Nombre);override;
end;entier=class(Nombre)
procedure afficher; override;
procedure add(x:Nombre); override;
end;rationnel=class
num,denom:entier;
end;