Comparaison polymorphisme
ou
tests de type if...then avec l'opérateur is


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.

 

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 TObject

    Equipe1-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);
     End
    if  S1 is Tliste then
     begin
       Tliste(S1).Initialiser;
       Tliste(S1).Tri;
       Tliste(S1).Ecrire(Form1.Edit1);
     end
     if  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);
    Begin
    if 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 is Tfichier then
     begin
       Tfichier(x)Tri;
       Tfichier(x).Ecrire(Form1.Edit1);
     end
    End;

    Equipe1-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);
    Begin
    if is Ttable then
      ...........
    Else
    if  x  is Tliste then
      ...........
    Else
    if is Tfichier then
      ...........
    Else // nouveau code pour TDatas
    if is TDatas then
     begin
       TDatas (x).Tri;
       TDatas (x).Ecrire(Form1.Edit1);
     End;
    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 :




    CLASSES DESCENDANT DE LA MEME CLASSE ABSTRAITE

    Equipe2-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 
    is Tliste then
         begin

            Tliste(x).Tri;
             Tliste(x).Ecrire(Form1.Edit1);
          end else
            if 
    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 METHODES

    Les 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.


    Conclusion

    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.