Polymorphisme

Introduction

La programmation orientée objet permet plusieurs avantages majeurs au niveau du code. Nous avons déjà vu que dans les avantages de ce paradigme, il y a la représentation plus intuitive de la programmation en représentant les structures en se basant sur des éléments réels (Produit, Animal, Personnes, etc.) au lieu de se baser sur des structures abstraites de donnée (Tableau, Liste, Arbre, etc.) Un autre avantage est de faciliter la réutilisation de code, ce qui présente une multitude d’avantages(diminution de la duplication de code, facilite la maintenance, etc.)

Un autre des avantages de la programmation orientée objet est la possibilité d’utiliser du polymorphisme. Grâce à cette mécanique, il est possible de généraliser le code de manière à gérer une grande quantité d’objets d’un même coup. Encore une fois, cette mécanique permet plusieurs avantages, tels qu’avoir du code plus logique, encore diminuer la duplication de code ainsi que faciliter la maintenance du logiciel.

Qu’est-ce que le polymorphisme

On dit qu’une assignation (incluant le passage d’argument) est polymorphique dans le cas ou le type de l’expression source est différent du type de la cible de l’assignation.

On appelle polymorphisme la mécanique permettant à une assignation d’être polymorphique.

Cette description peut paraître étrange à première vue, mais je vais préciser par un exemple. Pour présenter l’exemple, je vais utiliser l’arbre d’héritage suivant:

Donc, soit le code suivant:

new Chien()

Nous voyons que cette expression crée une instance de type « Chien ».

Maintenant, prenons la déclaration:

Animal monAnimal

On voit que la variable « monAnimal » est de type « Animal ».

Maintenant, prenons l’assignation suivante:

Animal monAnimal = new Chien();

Cette assignation est valide puis que si « Chien » hérite d’Animal, on a que « Chien » est un « Animal ». Et si « Chien » est un « Animal », donc un objet de type « Chien » peut être assigné à une variable de type « Animal » (comme dans cet exemple).

Comme dit plus haut, un passage d’argument peut également être polymorphique. Si je prend, par exemple le code suivant:

/**
 * Appel un animal par son nom.
 *
 * @param aAnimal L'Animal à appeler
 **/
public void appelerAnimal(Animal aAnimal)  {
    System.out.println("Vient ici " + aAnimal.getNom());
}

Donc, le passage d’argument suivant est polymorphique:

Chien lChien = new Chien("Fido");
appelerAnimal(lChien);  // Passage d'argument polymorphique

À noter

Voici certaines informations qu’il est pertinent de prendre en compte à propos du concept de polymorphisme.

  • Lorsqu’un objet peut être assigné à une variable, on dit que le type de l’objet est conforme au type de la variable.
  • En programmation orientée objet, un type A est conforme à un type B si et seulement si A est un descendant de B.
  • La relation polymorphique n’est pas commutative. En d’autres mots, si, dans l’exemple précédent, on voit que le type « Chien » est conforme au type « Animal », mais il est inexact de dire que le type « Animal » est conforme au type « Chien ». Donc, l’expression suivante est illégale:
    Chien monChien = new Animal();
  • Le polymorphisme n’est pas une conversion. Malgré qu’objet est assigné à une variable d’un certain type, le type de l’objet ne change pas.
  • Le polymorphisme peut également être utilisé comme type générique d’une collection (comme une liste par exemple). Voici un exemple:
    List<Animal> maListe = new ArrayList<Animal>(5);
    maListe.add(new Chien("Fido"));
    maListe.add(new Chat("Minou"));
    maListe.add(new Chien("Rex"));
    maListe.add(new Chat("Minet"));
    maListe.add(new Chien("Bull"));
    

Le « cast » de type

Comme nous l’avons vu plus haut, le polymorphisme n’est pas une conversion. Le fait que l’objet est assigné à une variable d’un autre type ne fait pas en sorte que l’objet change de type. Il devrait donc être possible d’assigner une variable d’un certain type, vers un type descendant, pourvu que le type de l’objet soit conforme au type descendant. Par exemple:

Animal lAnimal = new Chien("Fido");
Chien lChien = lAnimal;

Ici, on voit que puisque l’on sait que l’objet inclue dans la variable « lAnimal » est de type « Chien », il ne devrait pas être problématique d’effectuer l’assignation vers la variable « lChien ».

Par contre, il est important de constater que Java doit toujours s’assurer, à la compilation, que les types sont valides avant d’effectuer une validation. Et puisque, ici, le type « Chien » n’est pas conforme au type « Animal », cette assignation sera refusée. Il est important de comprendre que le compilateur Java n’a pas la responsabilité d’analyser le code pour déduire le type d’un objet. Il ne travaille qu’avec les types des expressions (ici, le type des variables).

Il est, malgré tout, possible de spécifier au compilateur Java que l’expression doit être considéré d’un certain type. Pour se faire, on utilise un « cast » de type. Voici l’exemple précédent, utilisant un « cast » de type:

Animal lAnimal = new Chien("Fido");
Chien lChien = (Chien)lAnimal;

En effectuant un « cast », je force d’une certaine manière Java à considérer mon expression comme étant valide. Il est à noter qu’encore une fois, il ne s’agit pas d’une conversion (même si la syntaxe est la même que pour une conversion de types primitifs). Si le « cast » n’est pas valide, un exception de type « ClassCastException » sera lancée par Java. Par exemple, ce code lancera un exception:

Animal lAnimal = new Chien("Fido");
Chat lChat = (Chat)lAnimal;

Validation de type

Les exemples de « cast » précédents sont bien entendu un peu simplistes. C’est la raison pour laquelle aucune validation n’est nécessaire pour effectuer les « cast ». Dans la réalité, il est rarement si clair de savoir quel est le type d’un objet. Par exemple, supposons que nous voudrions créer une méthode qui lance « miauler » dans le cas où l’objet est un « Chat » et « aboyer » dans le cas où l’objet est un « Chien ». Puisque « Chat » et « Chien » est un « Animal », je peux créer ma méthode avec cette signature:

public void criAnimal(Animal aAnimal) {
    ...
}

Ensuite, il faut exécuter des instructions différentes en fonction du type de l’argument. Il est possible d’effectuer ce travail avec du « cast » et des « try », mais le résultat sera peu élégant. Java propose une mécanique simple pour effectuer ce travail. Nous pouvons utiliser l’expression « instanceof ». Voici un exemple:

public void criAnimal(Animal aAnimal) {
    if (aAnimal instanceof Chien) {
        ((Chien)aAnimal).aboyer();
    } else if (aAnimal instanceof Chat) {
        ((Chat) aAnimal).miauler();
    }
}

Également, l’exemple précédent nous montre qu’il est possible d’effectuer un « cast » sans nécessairement que ce « cast » servent à une assignation.

Retour

 


Auteur: Louis Marchand
Creative Commons License
Sauf pour les sections spécifiées autrement, ce travail est sous licence Creative Commons Attribution 4.0 International.