Problème du diamant

Un article de Wikipédia, l'encyclopédie libre.
Aller à : navigation, rechercher
Un diagramme d'héritage en diamant.

Le problème du diamant (ou problème du losange dans certains articles scientifiques) arrive principalement en programmation orientée objet, lorsque le langage permet l'héritage multiple. Si une classe D hérite de deux classes B et C, elles-mêmes filles d'une même classe A, se pose un problème de conflit lorsque des fonctions ou des champs des classes B et C portent le même nom. Le nom de ce problème provient de la forme du schéma d'héritage des classes A, B, C et D dans ce cas.

Par exemple, dans le cas d'une interface graphique, une classe Button pourrait hériter de deux classes Rectangle (gérant son apparence) et Clickable (gérant les clics de souris) et ces deux classes hériter d'une classe Object. Si la classe Object définit la fonction equals (gérant la comparaison entre objets), qu'une des deux sous classes Rectangle ou Clickable (ou les deux) étendent cette fonction pour l'adapter à leurs particularités, laquelle des fonctions equals de Rectangle ou de Clickable la classe Button doit elle utiliser ? Choisir arbitrairement une seule des deux fonctions ferait perdre l'intérêt de l'héritage; utiliser les deux fonctions pose le problème de l'ordre des appels, de la combinaison des résultats (ou des erreurs), de l'éventuelle redondance de leurs effets, etc.

Approches de résolution[modifier | modifier le code]

En interdisant les homonymies[modifier | modifier le code]

En C++ ces problèmes sont appelées des ambiguïtés : le compilateur lève une erreur s'il ne parvient pas à déterminer la méthode à utiliser. Le développeur doit alors, soit redéfinir la méthode dans la classe dérivée, soit utiliser l'opérateur de résolution de portée (::) pour préciser la méthode à utiliser[1].

Des langages comme Eiffel ou Ocaml proposent des constructions plus évoluées pour aider le développeur à résoudre ces ambiguïtés.

En définissant un ordre de priorité[modifier | modifier le code]

Cela consiste à définir un algorithme de linéarisation pour construire un ordre de résolution des méthodes (MRO : Method Resolution Order). Une bonne linéarisation doit respecter certaines contraintes [2]:

  • acceptabilité : le MRO ne doit dépendre que des déclarations d'héritage, et pas par exemple du nom des classes
  • précédence locale : le MRO doit respecter l'ordre des classes parentes qui a été choisi par le programmeur lors de la définition de la classe dérivée
  • monotonie : si la classe B est avant la classe A dans le MRO d'une classe C alors la classe B devra être avant la classe A dans le MRO de toute classe dérivée de C.

Les langages de programmation peuvent résoudre ce problème de façons différentes :

  • Scala utilise la fonction définie dans la première classe, après avoir aplati l'arbre les traits par une recherche « right-first depth-first » et éliminé les doublons (ne conservant que le dernier des traits surnuméraires). Ainsi, l'ordre de résolution de l'exemple de l'introduction serait [D, C, A, B, A], réduit en [D, C, B, A]. Python 2.2 utilisait un algorithme similaire, il a été abandonné car il ne respecte pas la contrainte de monotonie dans certaines situations[3]
  • Dylan, Perl et Python utilisent l'algorithme C3[4]. Il respecte les trois contraintes d'acceptabilité, de précédence locale et de monotonie; mais interdit certaines constructions (par exemple E dérivée de C, D si C dérivée de A, B et D dérivée de B, A)

En réservant l'héritage multiple aux interfaces[modifier | modifier le code]

D'autres langages interdisent plus simplement l'héritage multiple (comme Ada, Ruby, Objective-C, PHP, C#, Delphi/Free Pascal et Java): une classe n'hérite que d'une seule autre, mais peut implémenter une liste d'Interfaces. Ces interfaces ne contiennent que des méthodes abstraites, c'est-à-dire ne contenant aucun code. C'est à la classe C d'implémenter toutes les méthodes de toutes ses interfaces. Les interfaces sont dans ce cas assimilables à des contrats forçant toutes les classes les utilisant d'implémenter les mêmes méthodes.

Notes et références[modifier | modifier le code]

Bibliographie[modifier | modifier le code]

  • Eddy Truyen, Wouter Joosen, Bo Nørregaard Jørgensen, Pierre Verbaeten, « A Generalization and Solution to the Common Ancestor Dilemma Problem in Delegation-Based Object Systems », Proceedings of the 2004 Dynamic Aspects Workshop, no 103–119,‎ 2004 (lire en ligne)