Utilisation du polymorphisme pour réduire la complexité

On l’a vu, la complexité du code nuit à sa bonne compréhension et à sa maintenabilité. Prennons un exemple concret pour voir comment le polymorphisme en C++ peut nous aider à réduire la complexité de certaines fonctions.

Les périphériques de marques différentes

Considérons une fonction qui demande à un périphérique d’effectuer un traitement. Cependant, le périphérique en question peut être d’une marque ou d’un modèle différent; ce qui implique selon le cas, un traitement différent, ou une manière spécifique de s’adresser au dispositif concerné.

Typiquement, notre fonction va ressembler à ça:

void Peripherique::traitement() {
  if (AVHIRAL == getMarque()) {
    traitementAvhiral();
  else if (ALCIOM == getMarque()) {
    traitementAlciom();
  }
  else {
    traitementAutre();
  }
}

Complexité cyclomatique 3. Voyons comment la réduire à 1.

Le polymorphisme

Le polymorphisme décrit la capacité d’un objet d’être vu sous diverses formes. Pour le comprendre, regardons d’abord à quoi ressemble le shéma UML de la classe Peripherique dans notre exemple.

Une seule classe objet, Peripherique dans ce diagramme

Ce que l’on peut dire, c’est que lui il n’est pas polymorphe. Tout code appelant va avoir affaire à un objet de la classe Perpherique, et c’est juste un état interne (la marque) qui va permettre d’adapter le comportement si besoin.

Regardons maintenant le schéma UML de la même classe, mais conçue de manière polymorphique.

PeripheriqueAlciom et PeripheriqueAvhiral héritent de Peripherique

Ce qui saute aux yeux, c’est que certes, il y a plus de classes, mais il y aussi moins de noms de fonctions !

Et le code, regardez maintenant le code:

void Peripherique::traitement() {
  // cas général
}

void PeripheriqueAlciom()::traitement() {
  // cas specifique Alciom
}

void PeripheriqueAvhiral::traitement() {
  // cas specifique Avhiral
}

Donc la magie du polymorphisme est la suivante: le code appelant dispose d’un pointeur vers un Peripherique, sans savoir sa marque. Mais au moment de lui demander un traitement, un petit bout de code spécifique au langage C++ résoud la question de savoir quelle fonction implémentante appeler.

Discussion sur l’opportunité de ce changement

On pourra objecter que cela ne sert à rien, puisque l’on a toujours trois fonctions spécifiques, et que si l’on somme leurs complexités respectives, on arrive toujours à 3, bref on a pas avancé.

Mais d’une part, il faut bien voir que ce qui est important pour le développeur, c’est la complexité cognitive, plus que la complexité cyclomatique. Et il se trouve que la première est moindre dans le cas polymorphé.

D’autre part, si le développeur doit mettre à jour le comportement uniquement pour un type de périphériques, il voit tout de suite tous les endroits où il doit modifier les méthodes: il s’agit de l’implémentation de la classe spécifique.