Contactez-nous

Polymorphisme : utiliser des objets de différentes classes de manière interchangeable

Découvrez le concept de polymorphisme en programmation orientée objet (POO) avec Python. Apprenez comment des objets de différentes classes peuvent être traités uniformément grâce à une interface commune (héritage et surcharge de méthodes).

Qu'est-ce que le polymorphisme ? Définition et avantages

Le polymorphisme (du grec "poly" = plusieurs, "morphe" = forme) est un principe fondamental de la programmation orientée objet qui permet à des objets de différentes classes d'être traités de manière uniforme s'ils partagent une interface commune.

En d'autres termes, le polymorphisme permet d'utiliser des objets de types différents de la même manière, sans avoir à se soucier de leur type exact, tant qu'ils implémentent les méthodes attendues.

Le polymorphisme repose souvent sur l'héritage et la surcharge de méthodes, mais en Python, il est également lié au concept de "duck typing" (voir plus loin).

Les avantages du polymorphisme sont :

  • Flexibilité : Vous pouvez écrire du code qui fonctionne avec des objets de différents types, sans avoir à écrire du code spécifique pour chaque type.
  • Extensibilité : Vous pouvez facilement ajouter de nouvelles classes qui s'intègrent avec le code existant, tant qu'elles implémentent l'interface commune.
  • Réutilisabilité : Vous pouvez réutiliser le même code pour traiter des objets de types différents.
  • Code plus générique : Le polymorphisme permet d'écrire du code plus abstrait et plus général, qui ne dépend pas des détails d'implémentation spécifiques de chaque classe.

Polymorphisme et héritage : une interface commune

L'héritage est un moyen courant de réaliser le polymorphisme. En créant une hiérarchie de classes avec une classe mère commune, vous pouvez définir une interface commune (un ensemble de méthodes) que toutes les classes filles implémentent (éventuellement en surchargeant les méthodes).

Exemple (repris des sections précédentes) :

class Animal:
    def faire_du_bruit(self):
        print("...")

class Chien(Animal):
    def faire_du_bruit(self):
        print("Wouf wouf !")

class Chat(Animal):
    def faire_du_bruit(self):
        print("Miaou !")

animaux = [Animal(), Chien(), Chat()]

for animal in animaux:
    animal.faire_du_bruit()  # Appel polymorphique

Dans cet exemple :

  • `Animal`, `Chien` et `Chat` partagent une interface commune : la méthode `faire_du_bruit`.
  • La boucle `for` peut traiter une liste d'objets de types différents (`Animal`, `Chien`, `Chat`) de manière uniforme, en appelant simplement la méthode `faire_du_bruit` sur chaque objet.
  • La méthode `faire_du_bruit` appelée dépend du type réel de l'objet à l'exécution (c'est le principe du "late binding" ou "dynamic dispatch").

C'est un exemple classique de polymorphisme : le même code (`animal.faire_du_bruit()`) se comporte différemment en fonction du type de l'objet `animal`.

Duck typing : "Si ça marche comme un canard et que ça cancane comme un canard, alors c'est un canard"

En Python, le polymorphisme est souvent associé au concept de "duck typing" (typage canard). Le duck typing est une forme de typage dynamique où le type ou la classe d'un objet est moins important que les méthodes qu'il définit.

L'expression "Si ça marche comme un canard et que ça cancane comme un canard, alors c'est un canard" illustre bien ce principe. En d'autres termes, si un objet a les méthodes et les attributs attendus, il peut être utilisé comme s'il était d'un type particulier, même s'il n'en hérite pas formellement.

Exemple :

class Chien:
    def faire_du_bruit(self):
        print("Wouf wouf !")

class Canard:
    def faire_du_bruit(self):
        print("Coin coin !")

class Robot:
  def faire_du_bruit(self):
    print("Bip Boop")

def faire_faire_du_bruit(animal):
    animal.faire_du_bruit()

f = Chien()
g = Canard()
r = Robot()

faire_faire_du_bruit(f)
faire_faire_du_bruit(g)
faire_faire_du_bruit(r)

Dans cet exemple, `Chien`, `Canard` et `Robot` n'héritent pas d'une classe mère commune, mais ils ont tous une méthode `faire_du_bruit`. La fonction `faire_faire_du_bruit` peut donc être appelée avec un objet de n'importe laquelle de ces classes.

Le duck typing rend le code Python très flexible et adaptable. Il permet d'utiliser des objets de différentes classes de manière interchangeable, tant qu'ils implémentent l'interface attendue.

Polymorphisme et méthodes spéciales

Le polymorphisme s'applique également aux méthodes spéciales. Par exemple, si vous définissez la méthode `__add__` dans plusieurs classes, vous pouvez utiliser l'opérateur `+` avec des objets de ces classes, même si elles n'ont pas de lien d'héritage direct.

Exemple :

class Livre:
    def __init__(self, nb_pages):
        self.nb_pages = nb_pages

    def __add__(self, other):
        return Livre(self.nb_pages + other.nb_pages)
    
    def __str__(self):
      return f'Livre de {self.nb_pages} pages'

class BD:
    def __init__(self, nb_pages):
        self.nb_pages = nb_pages

    def __add__(self, other):
        return BD(self.nb_pages + other.nb_pages)
      
    def __str__(self):
      return f'BD de {self.nb_pages} pages'

livre1 = Livre(150)
livre2 = Livre(200)
bd1 = BD(48)

print(livre1 + livre2) # Affiche Livre de 350 pages
print(livre1 + bd1) # Lève une erreur (TypeError). Ce n'est pas supporté car l'addition n'est possible qu'entre 2 objets de même type.

Dans cet exemple, `Livre` et `BD` n'ont pas de classe mère commune, mais ils définissent tous les deux `__add__`. On peut donc utiliser `+` avec des objets `Livre` et `BD`, mais le résultat dépendra du type des objets.

Bonnes pratiques et considérations

Voici quelques bonnes pratiques et considérations concernant le polymorphisme :

  • Concevez des interfaces claires : Définissez des interfaces claires et bien documentées pour vos classes, afin que les utilisateurs sachent quelles méthodes ils peuvent appeler.
  • Utilisez l'héritage de manière appropriée : L'héritage est un outil puissant, mais il ne doit pas être utilisé à tort et à travers. Utilisez-le lorsque vous avez une relation "est-un" (par exemple, un Chien *est un* Animal). Dans d'autres cas, la composition (créer des objets qui contiennent d'autres objets) peut être plus appropriée.
  • Privilégiez le duck typing (avec prudence) : Le duck typing rend le code Python flexible, mais il peut aussi le rendre moins sûr (car il n'y a pas de vérification de type statique). Utilisez-le avec discernement, et documentez clairement les interfaces attendues.
  • Testez votre code : Assurez-vous que votre code fonctionne correctement avec différents types d'objets, en particulier si vous utilisez le duck typing.

Le polymorphisme est un concept puissant qui peut rendre votre code plus flexible, plus extensible et plus facile à maintenir. En le maîtrisant, vous écrirez du code Python plus élégant et plus efficace.