Témoignage
 

Rubrique « Info »

 



Écrivez à AILES !



Retour vers programmation



Retour vers les questions Objet



Retour vers les questions sur le
Polymorphisme
 


Questionnaire Objet
Polymorphisme

[javaworld], [schupp], [histoiryLge], [eiffel]


    On retrouve le polymorphisme sous 4 formes différentes, mais une seule d'entre elle intervient dans le polymorphisme couramment manipulé dans un langage objet : le polymorphisme dynamique universel d'inclusion (ça en jette, non ?) 
    En examinant
l'utilisation du binding que fait ce polymorphisme, on peut en déduire ses avantages et mieux comprendre en quoi c'est une des caractéristiques majeurs de la programmation objet.

4 types de polymorphisme :
    [javaworld] mentionne la classification de Luca Caedelli et Peter Wegner, auteurs de "On Understanding Types, Data Abstraction, and Polymorphisme". [histoiryLge] complète en rajoutant que le même Wegner, dans "The Object-Oriented Classification Paradigm" (1988) précise que le terme a été introduit par Strachey en 1960.
    D'une manière générale, le polymorphisme représente la capacité d'une entité à posséder plusieurs formes. Ici, "l'entité" en question est le type.
    Cette classification a donc été initiée par Strachey et implique 2 catégories de polymorphismes, elles-même divisées en 2 :
    * "universal polymorphisme" : fonctionne sur un ensemble défini de types qui ont une structure commune ;
    * "ad-hoc polymorphisme" : fonctionne sur un nombre limité de types qui n'ont pas de lien entre eux ;

Plus en détail, cela donne :

   * "universal polymorphisme" : 2 mécanismes permettent de définir un "ensemble défini de type possédant une structure commune".
- l'un est à chercher dans le paradigme de programmation générique;
- l'autre dans l'héritage et le binding de type.
        => "parametric polymorphisme" : une seule abstraction s'applique à un ensemble de type. Typiquement, List<T> (lien) peut s'appliquer à tout type. Il suffit "d'instancier le template" pour une classe donné (les connaisseurs en C++ apprécieront) pour créer une liste "Liste<String>". Le lien précédent fait même allusion à la généricité contrainte, où la structure commune des types sur lesquels s'appliquent l'abstraction est un peu mieux défini. Dans ce cas, l'ensemble défini de type est limité.
Rq : Java, bien sûr, ne supporte pas, avec ses List(Object) un tel polymorphisme, 
      => "inclusion polymorphisme" (dit aussi "inheritance ou subtype polymorphisme) : la structure commune est issue d'une phénomène d'inclusion (un type est inclus dans un autre). Dans le cas des langages objets, cette inclusion prend la forme d'un héritage ou d'un "sous-typage", et l'ensemble défini de type est virtuellement... sans limite.

    * "ad-hoc polymorphism" : 2 mécanismes permettent de fonctionner "sur un nombre limité de type non liés entre eux" :
- l'un la conversion de type ;
- l'autre utilise la surcharge;
    Comme on va le voir, ces variétés de polymorphisme sont plus des facilités de programmation que de vrais "polymorphismes génériques".
        => "coercion" : conversion d'un type vers celui attendu par un opérateur ou une fonction, ce qui permet d'éviter les erreurs de compilation. Son expression la plus connue est le casting.
Ce casting est soit implicite, soit explicite.
> Il est fait automatiquement par le compilateur pour les types de base (int en double, etc.) pour peu que l'opérateur ou la fonction qui en a besoin est bien définie pour les types castés.
Ainsi, 1.0 + 1 marche parce que le langage Java défini un opérateur + acceptant 2 arguments de type "double" et que le langage implicitement converti le deuxième argument "int" en "double".
> Il est fait explicitement par le programmeur, pour peu que le type casté fasse bien parti de la hiérarchie du type attendu (cf. ex. plus bas).
        => "overloading" : permet l'usage du même opérateur ou du même nom de méthode pour plusieurs significations distinctes de programmation (cf. dynamic binding ou dynamic dispatching). Java supporte certains opérateurs surchargés, mais ne permet pas la définition de ses propres opérateurs. Java supporte la surcharge des noms de méthodes, pour peu que les signatures soient différentes.

Variable polymorphe ?
Une variable peut être polymorphe au sens coercion (ad-hoc) ou inclusion (universal).
Exemple:
class stack{};
class counting_stack : public stack{
  public : int size();
  public : void push(int);
}


coercion
stack s;
counting_stack cs;

s=cs ; // coercion de cs dans s : la zone mémoire allouée à s est plus petite que celle de cs : tout un bout de cs "est tronqué" et n'est pas recopié dans celle de s!

cs=s ; // illégal : la zone mémoire allouée à cs est plus grande que celle de s... donc on ne sait pas quoi recopier dans la zone mémoire propre à cs, à savoir celle qui contient la méthode size() !

inclusion
stack         * sp  = new stack;
counting_stack* csp = new counting_stack;
sp=csp;  // correct car le type de sp est parent de celui de csp. Aucune zone mémoire n'est ici tronquée : les 2 variables pointent sur la même adresse. Simplement, la
table virtuelle des fonctions sera moins étendue via sp que via csp.


Le lien entre polymorphisme et binding
    Il s'agit du dynamic binding, qui, dans le cas des fonctions, devient le "dynamic dispatching" et qui s'appuie sur le polymorphisme d'inclusion (de hiérarchie de type).
Si l'on reprend nos stack et counting_stack, pour illustrer le dynamic binding (ou polymorphisme dynamique pour fonctions) : 
stack* s=new stack;
counting_stack* cs=new counting_stack;
sp-> push(1);
csp-> push(2);
sp=csp; // sp, variable polymorphique d'inclusion
sp->push(3); // quel push est appelé ?


2 réponses possibles, en fonction du moment où le binding intervient :
- stack::push est appelé ; il s'agit d'un binding statique;
- counting_stack::push est appelé, le binding est donc dynamique. On retrouve ce binding dans la réponse suivante.

    Les fonctions virtuelles en C++ se basent sur le polymorphisme d'inclusions et ne sont mises en œuvre que via des variables polymorphes. cf. le mécanisme des tables virtuelles.

Polymorphic attachment
    Avant de voir en quoi le polymorphisme intervient dans les liaisons, il faut bien préciser ce que l'on relie.
   D'un côté, vous avez une variable qui possède un type.
   De l'autre vous avez un objet, instance d'une classe.
   Lorsque vous assignez l'objet à la variable typée, vous faites un binding de valeur.
   Ex :
Base baseVar = new Derived();

Attacher plusieurs références à un objet
   Si l'on considère la hiérarchie de classe suivante :
   Derived2 qui hérite de Derived qui hérite de Base, on peut associer à l'objet "new Derived2()" une variable de type Derived ou Base.
    L'effet dans ce cas est la réduction des fonctionnalités de l'objet (elles ne sont plus accessibles via l'interface associée au type de la variable). Mais tout appel aux fonctionnalité de la variable de type Base exécutera pourtant dans la bonne implémentation (celle de Derived2).
    C'est le polymorphisme dynamique d'inclusion ou "dynamic dispatching" dans la réponse précédente.
    Un premier intérêt de ce type de polymorphisme est donc de détacher le "quoi" (les fonctions de Base, qui doivent être les seules disponibles via la variable de type Base), du comment (l'implémentation de ces mêmes fonctions doit être celle de l'objet, ici "new Derived2()").


Attacher une références à plusieurs objet

   Si vous avez une classe qui possède la fonction void f(Base aBase) et que vous lui passez des variables "d" de type Derived, puis "d2" Derived2, vous associez successivement au même objet ("aBase") plusieurs références ("d" et "d2");
    Si cette fonction f appelle une méthode de Base (et elle ne peut faire autrement, vu son type), ce sera là encore la bonne implémentation qui sera appelée, et ce quelque soit la référence (Derived, Derived2, même Derived3 créée plus tard) passée à l'objet aBase.
    Un deuxième intérêt du polymorphisme dynamique d'inclusion est donc de pouvoir appeler les bonnes implémentations de classes... même si vous ne les avez pas encore écrite ! Tant qu'elles dériveront de Base, ce polymorphisme fonctionnera naturellement.
 
    Cette distinction entre la variable d'un côté, l'objet de l'autre permet de mieux aborder la différence qui existe entre type et interface.


               
 
Avertissement !
 
Décollage !  |  Présentation du site web "AILES"  | 
Infos générales  |  articles "Informatique"