64 votes

Le polymorphisme est-il possible sans héritage ?

Dans une interview, on m'a demandé si le polymorphisme pouvait être réalisé sans héritage. Est-ce possible ?

78voto

Edwin Dalorzo Points 19899

La meilleure explication sur le sujet que j'aie jamais lue est un article de Luca Cardelli un théoricien renommé de la typologie. L'article s'intitule Comprendre les types, l'abstraction de données et le polymorphisme .

Types de polymorphisme

Cardelli définit plusieurs types de polymorphisme dans cet article :

  • Universel
    • paramétrique
    • inclusion
  • Ad-hoc
    • surcharge
    • coercition

Le type de polymorphisme lié à l'héritage est classé comme polymorphisme d'inclusion ou polymorphisme de sous-type.

Wikipedia fournit une bonne définition :

Dans la programmation orientée objet, le polymorphisme de sous-type ou l'inclusion polymorphisme est un concept de la théorie des types dans lequel un nom peut dénoter instances de nombreuses classes différentes, à condition qu'elles soient reliées par une super classe commune. Le polymorphisme d'inclusion est généralement généralement supporté par le sous-typage, c'est-à-dire que les objets de types différents sont sont entièrement substituables aux objets d'un autre type (leur(s) type(s) de base). type(s) de base) et peuvent donc être gérés par une interface commune. Le polymorphisme d'inclusion peut également être réalisé par le biais de la coercition de type, également connue sous le nom de type coercition de type, également connue sous le nom de moulage de type.

Un autre article de Wikipedia intitulé Polymorphisme dans la programmation orientée objet semble répondre à vos questions également.

En Java

Cette fonctionnalité de sous-typage en Java est réalisée, entre autres, par l'héritage des classes et des interfaces. Bien que les caractéristiques de sous-typage de Java ne soient pas toujours évidentes en termes d'héritage. Prenez par exemple les cas de covariance et de contravariance avec les génériques. De même, les tableaux sont sérialisables et clonables, bien que cela ne soit pas évident dans la hiérarchie des types. On peut également dire que, grâce à la conversion par élargissement primitif, les opérateurs numériques de Java sont polymorphes, acceptant même dans certains cas des opérandes totalement indépendants (par exemple, la concaténation de chaînes de caractères et de nombres ou d'une chaîne de caractères et d'un autre objet). Considérons également les cas de mise en boîte et de mise hors boîte des primitives. Ces derniers cas de polymorphisme (coercition et surcharge) ne sont pas du tout liés à l'héritage.

Exemples

Inclusion

List<Integer> myInts = new ArrayList<Integer>();

C'est le cas auquel votre question semble faire référence, c'est-à-dire lorsqu'il existe une relation d'héritage ou d'implémentation entre les types, comme dans le cas présent où ArrayList implémente List.

Comme je l'ai mentionné, cependant, lorsque vous introduisez des génériques Java, les règles de sous-typage deviennent parfois floues :

List<? super Number> myObjs = new ArrayList<Object>();
List<? extends Number> myNumbers = new LinkedList<Integer>();

Et dans d'autres cas, les relations ne sont même pas évidentes dans l'API.

Cloneable clone = new int[10];
Serializable obj = new Object[10]

Malgré tout, tous ces éléments, selon Cardelli, sont des formes de polymorphisme universel.

Paramétrique

public <T> List<T> filter(Predicate<T> predicate, List<T> source) {
  List<T> result = new ArrayList<>();
  for(T item : source) {
    if(predicate.evaluate(item)){
         result.add(item);
    }
   return result;
  }
}

Le même algorithme peut être utilisé pour filtrer toutes sortes de listes avec toutes sortes de prédicats sans avoir à répéter une seule ligne de code pour chaque type de liste possible. Le type de la liste réelle et le type de prédicat sont paramétriques. Voir cet exemple avec les expressions lambda disponibles dans Aperçu du JDK 8 (pour la brièveté de l'implémentation du prédicat).

filter(x -> x % 2 == 0, asList(1,2,3,4,5,6)); //filters even integers
filter(x -> x % 2 != 0, asList(1L,2L,3L,4L,5L,6L)); //filters odd longs
filter(x -> x >= 0.0, asList(-1.0, 1.0)); //filters positive doubles

Selon Cardelli, il s'agit d'une forme de polymorphisme universel.

Coercition

double sum = 1 + 2.0;

L'arithmétique des nombres entiers et celle des nombres flottants sont totalement différentes. L'application de l'opérateur plus à deux opérandes de types différents est ici impossible sans une forme de coercition.

Dans cet exemple, les types integer et double sont automatiquement convertis en type double sans qu'il soit nécessaire d'effectuer un cast explicite. L'expression entière est promue en double. Il en est ainsi parce qu'en Java, nous avons des conversions d'élargissement primitives.

Selon Cardelli, cette forme de coercition automatique est une forme de polymorphisme ad-hoc prévue pour l'opérateur plus.

Il existe des langages dans lesquels il n'est même pas possible d'additionner un nombre entier et un nombre à virgule flottante sans un cast explicite (par exemple, à ma connaissance, SML, dans lequel, soit dit en passant, le polymorphisme paramétrique est essentiel pour surmonter ce type de problèmes).

Surcharge de travail

double sum = 2.0 + 3.0;
String text = "The sum is" + sum;

L'opérateur plus signifie ici deux choses différentes selon les arguments utilisés. De toute évidence, l'opérateur a été surchargé. Cela implique qu'il a différentes implémentations selon les types d'opérandes. Selon Cardelli, il s'agit d'une forme de polymorphisme ad hoc fourni pour l'opérateur plus.

Bien entendu, cela s'applique également aux formes de surcharge de méthodes dans les classes (par exemple, les méthodes java.lang.Math min et max sont surchargées pour prendre en charge différents types de primitives).

Dans d'autres langues

Même si l'héritage joue un rôle important dans la mise en œuvre de certaines de ces formes de polymorphisme, il n'est certainement pas le seul moyen. D'autres langages qui ne sont pas orientés objet fournissent d'autres formes de polymorphisme. Prenons, par exemple, les cas de dactylographie du canard dans des langages dynamiques comme Python ou même dans des langages à typage statique comme Go, ou encore types de données algébriques dans des langages comme SML, Ocaml et Scala, ou encore classes de type dans des langages comme Haskell, méthodes multiples en Clojure, héritage prototypique en JavaScript, etc.

2 votes

Excellente réponse. Ce serait encore mieux si vous la commenciez par un résumé précis et clair pour répondre à la question.

0 votes

@RichardSitze J'ai essayé d'obtenir une explication plus détaillée. J'espère ne pas avoir obscurci le sens du message original.

2 votes

Wow en fait, j'essayais de suggérer une phrase en haut de page pour dire : Oui ou non. Le lecteur a alors une idée de ce qu'il va faire en lisant le reste. Aussi, comme Stephen C a souligné, la réponse peut être différente Java (qui était la question originale), puis pour d'autres langues. Bon travail !

8voto

Amitabha Roy Points 106

Polymorphisme ad hoc > Surcharge d'opérateurs > Sans hérédité

Polymorphisme ad hoc > Surcharge des méthodes > Sans hérédité

Polymorphisme ad hoc > Chevauchement de méthodes > Avec hérédité

Polymorphisme paramétrique > Génériques > Sans héritage

Polymorphisme de sous-type ou polymorphisme d'inclusion > Affectation polymorphe > Avec hérédité

Polymorphisme de sous-type ou polymorphisme d'inclusion > Type de retour polymorphe > Avec hérédité

Polymorphisme de sous-type ou Polymorphisme d'inclusion > Type d'argument polymorphe > Avec hérédité

Polymorphisme de coercition > Élargissement > Avec ou sans hérédité

Polymorphisme par coercition > Auto boxing et unboxing > Sans hérédité

Polymorphisme de coercition > Var args > Sans hérédité

Polymorphisme de coercition > Casting de type > Sans héritage

5voto

Max Points 3119

Bien sûr. En Java, vous pouvez avoir deux classes qui implémentent la même interface, et leurs résultats sont polymorphes. Aucune fonctionnalité n'est héritée.

public interface Foo {
  public int a();
}

public class A implements Foo {
  public int a() {
    return 5;
  }
}

public class B implements Foo {
  public int a() {
    return 6;
  }
}

Puis ailleurs :

Foo x = new A();
System.out.println(x.a())
Foo y = new B();
System.out.println(y.a())

Les deux sites x y y sont Foo mais ils ont des résultats différents lorsque vous appelez a() .

7 votes

L'interface est héritée. Le contrat sémantique (jamais inforceble) est implicitement hérité. Voir la réponse d'Edwin.

1 votes

Bien, c'est juste. J'ai supposé que l'auteur de la question voulait dire "héritage d'implémentation". Sans autre précision de la part de l'enquêteur, et surtout dans le cadre d'un entretien orienté Java, cette réponse est probablement celle qu'ils recherchent. Il existe, bien sûr, des réponses beaucoup plus détaillées.

0 votes

Oui, je suis d'accord. J'ai tendance à considérer les interfaces (à tort ou à raison) comme un moyen de garantir au compilateur qu'un objet aura une certaine méthode avec une certaine signature, ce qui me fait penser au typage des canards, qui n'implique pas vraiment l'héritage. Techniquement, c'est considéré comme de l'héritage, bien sûr.

0voto

Dhananjay Points 1047

La surcharge de fonctions est l'un des polymorphismes (bien qu'il ne s'agisse pas du véritable polymorphisme) qui peut être réalisé sans héritage.

par exemple

class Foo { 
public void Arrest( Animal A){
    /*code...*/
 }  
public void Arrest( Terrorist T ) {
    /*code...*/
 }  

}

from main :

Foo f= new Foo();
f.Arrest( new Lion() );
f.Arrest(new Terrorist());

La méthode Arrest est appelée 2 fois mais le chemin d'exécution du code est différent.

*Encore une fois, ce n'est pas une vraie forme de polymorphisme. Le vrai polymorphisme en général ne peut être réalisé sans héritage.

0 votes

Les langages qui ne sont pas orientés objet fournissent des formes de polymorphisme qui ne s'appuient pas sur l'héritage (i.e. polymorphisme paramétrique ). Cela est en partie possible en Java grâce aux génériques.

0 votes

@EdwinDalorzo : et quelle est la définition du polymorphisme dans ces langages ? elle n'est peut-être pas la même que dans oop.

0voto

Speransky Danil Points 11150

Oui, je pense qu'ils voulaient probablement entendre parler de polymorphisme par interfaces. Donc s'il y a 2 classes qui implémentent la même interface, alors on peut l'utiliser dans tous les endroits où l'on attend un objet avec cet intervalle. Voir le code de wikipedia :

// from file Animal.java

public interface Animal {
        public String talk();
}

// from file Cat.java

public class Cat implements Animal {
        @Override
        public String talk() {
                return "Cat says Meow!";
        }
}

// from file Dog.java

public class Dog implements Animal {
        @Override
        public String talk() {
                return "Dog says Woof! Woof!";
        }
}

// from file PolymorphismExample.java

public class PolymorphismExample {

        public static void main(String[] args) {
                Collection<Animal> animals = new ArrayList<Animal>();
                animals.add(new Cat());
                animals.add(new Dog());

                for (Animal a : animals) {
                        System.out.println(a.talk());
                }
        }

}

0 votes

J'ai copié la réponse de Max jusqu'à l'erreur où B implémente goo au lieu de foo.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X