Le polymorphisme dans l’approche orientée objets

 

 

 

 

 

Définition et généralités

 

Le polymorphisme est en général entendu comme le polymorphisme dit « strict », celui qui nécessite héritage et redéfinition (fig1). On parle aussi parfois d’un polymorphisme de surcharge, qui est décrit dans les derniers paragraphes de ce billet.

 

Le polymorphisme est la possibilité d’envoyer un message à un objet sans connaître son type réel, alors que la méthode déclenchée est fonction du type réel. Dans les langages C++, C# et Java, pour obtenir du polymorphisme strict, il faut créer des relations de généralisation (héritage), et s’assurer de la redéfinition de méthodes. Pour des langages moins « statiquement » typés, le polymorphisme s’obtient plus simplement. Enfin, un polymorphisme « au sens large » correspond à l’utilisation de la surcharge et des classes génériques.

 

 

 

Fig 1 : La mise en œuvre du polymorphisme strict passe souvent par l’établissement d’une relation de généralisation et une redéfinition d’une ou plusieurs méthodes.

 

Le modèle de la figure 1 est statique, c’est-à-dire qu’il ne montre pas COMMENT ça fonctionne, mais plutôt le support du polymorphisme, dans les classes et les relations qu’elles ont entre elles. Pour le COMMENT, il faut montrer le modèle ou le code dynamique, qui mettra en évidence les appels.

 

Le polymorphisme est un mécanisme « runtime », qui sert à l’exécution.

 

Le polymorphisme strict

 

Pourquoi utiliser le polymorphisme ?

 

 

 

Le polymorphisme permet de ne pas connaître exactement le type de l’objet que l’on manipule pour écrire le code d’appel (envoyer un message).

 

Felin minou = new Chat() ;

minou.parler() ;

 

Fig 2 : code d’appel qui correspond à du polymorphisme : la deuxième ligne ne montre pas que le parler qui va être appelé est bien celui du Chat.

 

 

 

Les méthodes réellement appelées lors de l’exécution seront les méthodes des classes concrètes, cela veut donc dire que lors de l’exécution, un mécanisme (quelconque et variable selon les implémentations, les langages, etc) sera à l’œuvre pour « router » l’appel vers la bonne méthode.

 

Voici un gribouillage qui peut aider à comprendre ce routage :

 

 

 

Fig 3 : sur un objet Chat, un pointeur nous renvoie sur la table des fonctions, qui route l’appel vers la version parler… du Chat et non du Félin. Un objet Chat est en effet constitué par sa partie Félin et sa partie Chat.

 

La plupart des frameworks objets reposent sur la technique du polymorphisme. Par exemple, en Java Swing, il faut dériver une classe de JComponent et implémenter sa propre version de <paint> pour créer son nouveau type contrôle graphique. Cette méthode <paint> est appelée par le framework. Le principe OCP (Open Close Principle) repose sur cette technique : vous devez chercher les classes à dériver dans le framework plutôt que venir modifier les classes du framework et le relivrer !

 

Mise en œuvre en Java

 

Java est un langage assez strictement conforme aux concepts objets. Pour mettre en œuvre le polymorphisme, vous devez disposer de :

 

  • Classes en relation de généralisation : classe mère-classe fille

  • Une redéfinition de méthodes, déclarées sur la classe mère, et réécrite (différemment, normalement) sur la classe fille

 

 

 

Il n’y a rien d’autre à faire. Pour éviter les erreurs dues à une mauvaise orthographe, ou bien aux fautes de frappes, il est prévu depuis Java6, de rajouter l’annotation @Override sur la redéfinition dans la classe fille.

 

 

 

class Felin {

public void parler() {….}

}

class Chat extends Felin {

            @Override

public void parler() {…}

}

 

Fig 4 : la classe Chat est très facilement dérivée de la classe Felin.

 

 

 

Mise en œuvre en C++

 

 

 

C++ ne permet pas au polymorphisme de fonctionner par défaut. Il faut donc prévoir le polymorphisme dans la classe mère. Pour cela, ajouter le mot-clé virtual devant la fonction membre (la méthode) concernée. Ce mot clé va typiquement provoquer la génération d’une « vtable » ou table des fonctions virtuelles, qui routera les appels. Chaque objet doit donc être accompagné d’un pointeur sur cette « vtable ». Le coût induit peut être important. L’empreinte mémoire augmentée, mais aussi le coût de l’indirection. Ce dernier semble être d’environ 20% dans les compilateurs modernes.

 

 

 

class Felin {

public :

            virtual void parler() ;

} ;

class Chat : public Felin {

public :

            void parler();

} ;

 

 

 

Fig 5 : la classe Chat hérite facilement du Félin (ne pas oublier de faire un héritage public), mais si la classe mère n’a pas déclarée la fonction membre comme virtual, le polymorphisme ne fonctionnera pas. Seul le code de déclaration est montré dans la figure.

 

 

 

Mise en œuvre en C#

 

Dans les langages phares du DotNet, C# et VB.Net, la mise en œuvre du polymorphisme suit les règles suivantes : il faut indiquer sur la méthode de la classe mère que l’on va redéfinir, et aussi sur la méthode redéfinie de la classe fille que l’on veut cette redéfinition. Si une incohérence apparaît, soit le compilateur signale une erreur (bloquante), soit un avertissement (non bloquant, si masquage).

 

Pour le C#, utilisez virtual sur la méthode de la classe mère, et override sur la méthode de la classe fille.

 

 

 

class Felin {

public virtual void parler() { … }

}

class Chat : Felin {

public override void parler() {…}

}

 

Fig 6 : le C#  impose les mots clés virtual sur la méthode de la classe mère, et override sur la méthode de la classe fille.

 

 

 

Mise en œuvre en VB.NET

 

 

 

Pour utiliser le polymorphisme en VB.NET, il faut, comme pour son cousin le C#, indiquer que la méthode dans la classe mère sera redéfinie, et aussi indiquer sur la méthode redéfinie sur la classe fille que cette méthode doit exister dans la classe mère. Sur la méthode concernée de la classe mère, utilisez Overridable et sur la méthode concernée de la classe fille utilisez Override

 

 

Class Felin

Public Overridable Sub Parler()

            REM

End Sub

End Class

Class Chat

Inherits Felin

Public Overrides Sub Parler()

REM

End Sub

End Class

 

Fig 7 : en VB.NET, il faut marquer les méthodes sur la classe mère comme sur la classe fille.

 

 

 

Le polymorphisme en Python

 

 

 

Python est un langage très souple, qui utilisera donc le polymorphisme sans vraiment de contrainte. L’appel de la méthode sera réalisé… si elle est bien présente sur l’objet, à l’exécution. Les appels sont possibles, même si les types ne sont pas reliés. Python n’a pas de compilation statique.

 

 

 

Le polymorphisme de surcharge

 

 

 

Le polymorphisme de surcharge n’est pas un polymorphisme qui nécessite de l’héritage. Mais simplement le fait d’appeler une fonction avec des paramètres différents.

 

 

 

class Ecran {

            public void affiche( int valeur) {….}

            public void affiche(String valeur) { …}

}

ecran.affiche(4) ;

ecran.affiche(« coucou ») ;

 

 

 

Fig 8 : sur cet écran, j’appelle la même fonction, mais avec des paramètres différents : le compilateur va connaître la bonne fonction à appeler, en choisissant celle qui correspond aux paramètres.

 

L’effet du polymorphisme n’est donc pas ici découvert à l’exécution, donc … le rôle dans votre application de ce type de codage est beaucoup moins significatif.

 

 

 

Le polymorphisme des classes génériques

 

 

 

Enfin, on peut parler de polymorphisme avec des classes génériques (classes template, patrons de classes, ou de fonctions) puisque le principe du template est de réaliser un modèle qui sera adapté par le compilateur à l’usage du développeur. Encore une fois, il s’agit d’un mécanisme lié à la compilation, n’offrant pas les possibilités de liaison retardée comme le polymorphisme strict.

 

Conclusion

 

 

 

L’utilisation du polymorphisme est indispensable à la plupart des frameworks objets. Il s’agit d’un mécanisme runtime, permettant d’écrire des appels dont les résultats ne sont pas connus au moment où on les écrits. L’extension du framework est à ce prix. Certains langages permettent une utilisation libre de ce mécanisme. Mais les langages statiquement compilés que sont les C++, Java, C# et VB.NET nécessite la mise en œuvre de la généralisation et la formalisation de la redéfinition des méthodes.