Objectifs :On souhaite construire une application utilisant des classes de gestion de structures de données de noms (chaînes). Ces classes devraient être capables de lancer une intialisation de la structure, de trier par ordre croissant les données, de les afficher dans un éditeur de texte. Nous voulons disposer d'une quatrième classe possédant une méthode générale d'édition d'une structure de données après son ordonnancement.
Au départ nous travaillons sur une classe contenant un tableau, une classe contenant une liste chaînée et une classe contenant un fichier. Chaque classe contient trois méthodes : Tri, Initialiser, et Ecrire qui ne seront qu'esquissées, car c'est la conception de la hiérarchie qui nous intéresse dans cet exemple et non le code interne. Le lecteur peut s'il le souhaite développer un code personnel pour toutes ces méthodes.
- Types de base
- classes dérivant de TObject
- classes dérivant d'une classe abstraite
- code simplifié des méthodes
- conclusion
LES TYPES DE STRUCTURES DE BASE PROPOSES
Nous proposons de travailler avec les types de données suivants :
type
Element=record
clef:integer;
info:ShortString;
end;Tableau=Array[1..100]of Element;
pointeur=^chainon;
chainon=record
data:Element;
suivant:pointeur
end;Fichier = file of Element;
Nous supposons avoir fourni ces informations à deux équipes de développement leur laissant le choix de l'organisation des classes.
La première équipe décide d'implanter les 3 classes à partir de la classe racine (TObject en Delphi).
L'autre équipe de développement a choisi pour le même problème d'implémenter les 3 classes à partir d'une hiérarchie de classes fondée sur une classe abstraite.
CLASSES DESCENDANT DE TObjectEquipe1-1°) L'équipe implémente une gestion de structure de données à partir de classes héritant toutes de la classe racine TObject avec des méthodes à liaison statique.
Ttable=class
T:Tableau;
procedure Tri;
procedure Initialiser;
procedure Ecrire(Ed:Tedit);
end ;
Tliste=class
L:pointeur;
procedure Tri;
procedure Initialiser;
procedure Ecrire(Ed:Tedit);
end ;
Tfichier=class
F:fichier;
procedure Tri;
procedure Initialiser;
procedure Ecrire(Ed:Tedit);
end ;Ce choix permet aux membres de l'équipe n°1 de déclarer et d'instancier des objets dans chaque classe :
var
S1:Ttable;
S2:Tliste;
S3:Tfichier;
S1:=Ttable.Create;
S1.Initialiser;
S1.Tri;
S1.Ecrire(Form1.Edit1);S2:=Tliste.Create;
S2.Initialiser;
S2.Tri;
S2.Ecrire(Form1.Edit1);S3:=Tfichier.Create;
S3.Initialiser;
S3.Tri;
S3.Ecrire(Form1.Edit1);L'équipe pourra utiliser le polymorphisme d'objet en déclarant une reférence d'objet de classe racine puis en l'instanciant selon les besoins dans l'une des trois classes. Il sera alors nécessaire de transtyper la reférence en testant auparavant par sécurité, l'appartenance de l'objet reférencé à la bonne classe :
var
S1:TObject;
if S1 is Ttable then
begin
Ttable(S1).Initialiser;
Ttable(S1).Tri;
Ttable(S1).Ecrire(Form1.Edit1);
Endif S1 is Tliste then
begin
Tliste(S1).Initialiser;
Tliste(S1).Tri;
Tliste(S1).Ecrire(Form1.Edit1);
endif S1 is Tfichier then
begin
Tfichier(S1).Initialiser;
Tfichier(S1).Tri;
Tfichier(S1).Ecrire(Form1.Edit1);
end<< S1:=Ttable.Create; >> << S1:=Tliste.Create; >> << S1:=TFichier.Create; >> Dans l'application, l'équipe n°1 construit une classe TUseData qui possède une méthode générale d'édition d'une structure de données après ordonnancement , cette méthode utilise le polymorphisme d'objet pour s'adapter à la structure de données à éditer :
TUseData=class
Procedure Editer ( x : Tobject);
end ;
C'est le paramètre formel de classe générale TObject qui est polymorphe, les valeurs effectives qu'il peut prendre sont : n'importe quel objet de classe héritant de Tobject.
Code de la méthode Editer de TuseData :
Procedure Editer ( x : Tobject);
BeginElse
if x is Ttable then
begin
Ttable(x).Tri;
Ttable(x).Ecrire(Form1.Edit1);
EndElse
if x is Tliste then
begin
Tliste(x).Tri;
Tliste(x).Ecrire(Form1.Edit1);
EndEnd;
if x is Tfichier then
begin
Tfichier(x)Tri;
Tfichier(x).Ecrire(Form1.Edit1);
endEquipe1-2°) L'équipe livre au client l'application contenant en de multiples endroits un appel à la méthode Editer.
Equipe1-3°) Deux mois après la livraison et la mise en place, nous souhaitons rajouter une nouvelle structure de données que nous nommons TDatas (par exemple de structure d'arbre binaire). L'équipe propose de continuer la même organisation en créant et en implantant une nouvelle classe dérivant de TObject :
TAutre=class
Data : TDatas;
procedure Tri;
procedure Initialiser;
procedure Ecrire(Ed:Tedit);
end ;Equipe1-4.a°) L'équipe 1 doit alors reprendre et modifier le code de la méthode Procedure Editer ( x : Tobject); de la classe TUseData, en y ajoutant le test du nouveau type TDatas comme suit :
Procedure Editer ( x : Tobject);
BeginElse
if x is Ttable then
...........Else
if x is Tliste then
...........Else // nouveau code pour TDatas
if x is Tfichier then
...........End;
if x is TDatas then
begin
TDatas (x).Tri;
TDatas (x).Ecrire(Form1.Edit1);
End;Cette démarche de rajout et de recompilation, les membres de l'équipe n°1 doivent l'accomplir dans chaque partie de l'application qui utilise la méthode Editer.
Equipe1-4.b°) L'équipe n°1 envoie ensuite au client :
- La nouvelle classe et son code,
- ainsi que la nouvelle version de toutes les parties de l'application qui font appel à la méthode Editer dont la précédente version est maintenant caduque.
CLASSES DESCENDANT DE LA MEME CLASSE ABSTRAITEEquipe2-1°) L'équipe de développement a choisi pour le même problème d'implémenter une hiérarchie de classes fondée sur une classe abstraite qui a été nommée TStructData.
TStructData=class
procedure Tri;virtual;abstract;
procedure Initialiser;virtual;abstract;
procedure Ecrire(Ed:Tedit);virtual;abstract;
end ;Toutes les autres classes dériveront de TStructData, et possèderont des méthodes à liaisons dynamiques afin d'utiliser ici le polymorphisme de méthodes :
Ttable=class (TStructData)
T:Tableau;
procedure Tri;override;
procedure Initialiser;override;
procedure Ecrire(Ed:Tedit);override;
end ;
Tliste=class (TStructData)
L:pointeur;
procedure Tri;override;
procedure Initialiser;override;
procedure Ecrire(Ed:Tedit);override;
end ;
Tfichier=class (TStructData)
F:fichier;
procedure Tri;override;
procedure Initialiser;override;
procedure Ecrire(Ed:Tedit);override;
end ;Ce choix permet aux membres de l'équipe n°2, comme pour ceux de l'équipe n°1, de déclarer et d'instancier des objets dans chaque classe :
var
S1:Ttable;
S2:Tliste;
S3:Tfichier;
S1:=Ttable.Create;
S1.Initialiser;
S1.Tri;
S1.Ecrire(Form1.Edit1);S2:=Tliste.Create;
S2.Initialiser;
S2.Tri;
S2.Ecrire(Form1.Edit1);S3:=Tfichier.Create;
S3.Initialiser;
S3.Tri;
S3.Ecrire(Form1.Edit1);L'équipe n°2 pourra utiliser le polymorphisme de méthode en déclarant une reférence d'objet de classe racine abstraite puis en l'instanciant selon les besoins dans l'une des trois classes. Mais ici, il ne sera pas nécessaire de transtyper la reférence ni de tester auparavant par sécurité, l'appartenance de l'objet reférencé à la bonne classe. En effet les méthodes Initialiser, Tri et Ecrire étant virtuelles, c'est le type de l'objet lors de l'exécution qui déterminera le bon choix :
Var S1:TStrucData;
<< S1:=Ttable.Create; >> << S1:=Tliste.Create; >> << S1:=TFichier.Create; >> S1.Initialiser;
S1.Tri;
S1.Ecrire(Form1.Edit1);Dans l'application, l'équipe n°2 comme l'équipe n°1, construit une classe TUseData qui possède une méthode générale d'édition d'une structure de données après ordonnancement , cette méthode utilise le polymorphisme d'objet et le polymorphisme de méthode pour s'adapter à la structure de données à éditer :
TUseData=class
Procedure Editer ( x : TStructData);
end ;
Méthode Editer de TuseData :
Méthode Editer de TuseData : équipe n°2
Méthode Editer de TuseData : équipe n°2
Procedure Editer ( x : TStructData);
Begin
x.Tri;
x.Ecrire(Form1.Edit1);
End;
Procedure TUseData .Editer ( x : Tobject );
Begin
if x is Ttable then
begin
Ttable(x).Tri;
Ttable(x).Ecrire(Form1.Edit1);
end else
if x is Tliste then
begin
Tliste(x).Tri;
Tliste(x).Ecrire(Form1.Edit1);
end else
if x is Tfichier then
begin
Tfichier(x)Tri;
Tfichier(x).Ecrire(Form1.Edit1);
end
End;
Equipe2-2°) L'équipe livre au client l'application contenant en de multiples endroit un appel à notre méthode Editer.Equipe2-3°) Deux mois après l'équipe n°2 s'est vu demander comme pour l'autre équipe, de rajouter une nouvelle structure de données (par exemple de structure d'arbre binaire). L'équipe n°2 crée et implante une nouvelle classe dérivant de la classe abstraite TStructData comme pour les 3 autres classes déjà existantes :
TAutre=class (TStructData)
Data : TDatas;
procedure Tri;override;
procedure Initialiser;override;
procedure Ecrire(Ed:Tedit);override;
end ;Grâce au polymorphisme d'objet et de méthode à liaison dynamique la méthode Editer fonctionne avec toutes les descendants de TstructData.
Equipe2-4.a°) L'équipe n°2 n'a pas à modifier le code de la méthode Procedure Editer ( x : TStructData).
Equipe2-4.b°) Il suffit à l'équipe n°2 d'envoyer au client la nouvelle classe et son code (toutes les parties de l'application qui font appel à la méthode Editer fonctionneront correctement automatiquement) !
IMPLANTATION DES METHODESLes deux équipes ont coopéré entre elles, le code des méthodes est le même quelque soit le choix effectué (hériter de TObject ou hériter d'une classe abstraite).
//////////////////// Les tableaux /////////////////////////
procedure Ttable.Tri;
begin
//algorithme de tri d'un tableau
T[1].info:=T[1].info+' : tableau trié'
end;procedure Ttable.Initialiser;
begin
T[1].clef:=100;
T[1].info:='Durand';//etc...
end;procedure Ttable.Ecrire(Ed:Tedit);
begin
Ed.Text:='Clef= '+inttostr(T[1].clef)+'//'+T[1].info
end;//////////////////// Les listes chaînées /////////////////////////
procedure Tliste.Tri;
begin
//algorithme de tri d'une liste ...
L.data.info:=L.data.info+' : liste triée'
end;procedure Tliste.Initialiser;
begin
new(L);
L.data.clef:=100;
L.data.info:='Durand';
L.suivant:=nil
end;procedure Tliste.Ecrire(Ed:Tedit);
begin
Ed.Text:='Clef= '+inttostr(L.data.clef)+'//'+L.data.info
end;//////////////////// Les fichiers /////////////////////////
procedure Tfichier.Tri;
var UnElement:Element;
begin
//algorithme de tri d'un fichier ...
AssignFile(F,'FichLocal');
reset(F);
read(F,UnElement);
CloseFile(F);
UnElement.info:=UnElement.info+' : fichier trié';
reset(F);
write(F,UnElement);
CloseFile(F)
end;procedure Tfichier.Initialiser;
var UnElement:Element;
begin
AssignFile(F,'FichLocal');
rewrite(F);
UnElement.clef:=100;
UnElement.info:='Durand';
write(F,UnElement); //etc...
CloseFile(F)
end;procedure Tfichier.Ecrire(Ed:Tedit);
var UnElement:Element;
begin
AssignFile(F,'FichLocal');
reset(F);
read(F,UnElement);
Ed.Text:='Clef= '+inttostr(UnElement.clef)+'//'+UnElement.info;
CloseFile(F);
end;end.
Le polymorphisme a montré dans cet exemple ses extraordinaires facultés d'adaptation et de réutilisation. Nous avons constaté le gain en effort de développement obtenu par l'équipe qui a choisi de réfléchir à la construction d'une hiérarchie fondée sur une classe abstraite. Nous conseillons donc de penser à la notion de classe abstraite et aux méthodes virtuelles lors de la programmation d'un problème avec des classes.