web-dev-qa-db-fra.com

Le polymorphisme est-il possible sans héritage?

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

53

La meilleure explication sur le sujet que j'ai jamais lu est un article de Luca Cardelli , un théoricien de type renommé. L'article est nommé Sur la compréhension des types, de l'abstraction des données et du 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érédité 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 le polymorphisme d'inclusion est un concept dans la théorie des types dans lequel un nom peut désigner des instances de nombreuses classes différentes tant qu'elles sont liées par une super-classe commune. Le polymorphisme d'inclusion est généralement pris en charge par le sous-typage, c'est-à-dire que les objets de différents types sont entièrement substituables aux objets d'un autre type (leur (s) type (s) de base) et peuvent donc être gérés via une interface commune. Alternativement, le polymorphisme d'inclusion peut être obtenu par la contrainte de type, également connue sous le nom de coulée de type.

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

En Java

Cette fonctionnalité de sous-typage dans Java est obtenue, entre autres, grâce à l'héritage des classes et des interfaces. Bien que les fonctionnalités de sous-typage de Java ne soient pas évidentes en termes d'héritage tout le temps. Prenons par exemple les cas de covariance et de contravariance avec les génériques. De plus, les tableaux sont sérialisables et clonables bien que cela ne soit évident nulle part dans la hiérarchie des types. On peut également dire que grâce à la conversion d'élargissement primitif, les opérateurs numériques Java sont polymorphes, acceptant même dans certains cas des opérandes totalement indépendants (c.-à-d. Concaténation de chaînes et de nombres ou d'une chaîne plus un autre objet). Considérez également les cas de boxing et unboxing de primitives. ces derniers cas de polymorphisme (coercition et surcharge) ne sont pas du tout liés à l'hérédité.

Exemples

Inclusion

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

C'est le cas auquel votre question semble se référer, c'est-à-dire lorsqu'il existe une relation d'héritage ou d'implémentation entre les types, comme dans ce cas 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 JDK 8 Preview (pour la brièveté de l'implémentation des prédicats).

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.

Contrainte

double sum = 1 + 2.0;

L'arithmétique en nombre entier et en virgule flottante est totalement différente. L'application de l'opérateur plus à deux opérandes de types différents ici est impossible sans une certaine forme de contrainte.

Dans cet exemple, les types entier et double sont automatiquement contraints (convertis) en type double sans transtypage explicite. L'expression entière est promue au double. En effet, dans 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 vous ne pourriez même pas additionner un entier et un nombre à virgule flottante sans transtypage explicite (c'est-à-dire AFAIK, SML, dans lesquels, soit dit en passant, le polymorphisme paramétrique est la clé pour surmonter ce type de problèmes).

Surcharge

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.

Ceci, bien sûr, s'applique également aux formes de surcharge de méthode dans les classes (c'est-à-dire que 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 lorsque l'héritage joue un rôle important dans la mise en œuvre de certaines de ces formes de polymorphisme, ce 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 typage du canard dans des langages dynamiques comme Python ou même dans des langages typés statiquement comme Go, ou types de données algébriques dans des langages comme SML, Ocaml et Scala, ou classes de types dans des langages comme Haskell, méthodes multiples dans Clojure, héritage prototypique dans JavaScript, etc.

66
Edwin Dalorzo

Polymorphisme ad hoc> Surcharge d'opérateur> Sans héritage

Polymorphisme ad hoc> Surcharge de méthode> Sans héritage

Polymorphisme ad hoc> Substitution de méthode> Avec héritage

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

Sous-type Polymorphisme ou Inclusion Polymorphisme> Affectation polymorphe> Avec héritage

Sous-type Polymorphisme ou Polymorphisme d'inclusion> Type de retour polymorphe> Avec héritage

Sous-type Polymorphisme ou Inclusion Polymorphisme> Type d'argument polymorphe> Avec héritage

Polymorphisme de coercition> Élargissement> Avec ou sans héritage

Polymorphisme de coercition> Boxing et unboxing automatiques> Sans héritage

Polymorphisme de coercition> Arguments var> Sans héritage

Polymorphisme de coercition> Coulée de type> Sans héritage

6
Amitabha Roy

Sûr. En Java, deux classes peuvent implémenter 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())

x et y sont Foos, mais ils ont des résultats différents lorsque vous appelez a().

5
Max

La surcharge de fonctions est l'un des polymorphismes (bien que ce ne soit pas le véritable polymorphisme) qui peut être obtenu 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 d'arrêt est appelée 2 fois mais le chemin d'exécution du code est différent.

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

0
Dhananjay

Oui, je pense qu'ils voulaient probablement entendre parler du polymorphisme par les interfaces. Donc, s'il y a 2 classes qui implémentent à partir de la même interface, alors nous pouvons utiliser dans tous les endroits où nous attendons un objet avec une telle 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
Danil Speransky

type statique

surcharge - ce qui signifie plusieurs méthodes avec le même nom mais une signature différente qui est possible sans redéfinition

class StaticPolyExample
{
void print(int s)
{
    //print s
}

void print(String s)
{
    //print s
}

}

Type dynamique

prioritaire - ce qui signifie que la méthode dans la super classe sera redéfinie dans la sous-classe qui a besoin d'héritage

class Printer
{
void print(String s)
{
  // prints String
}

}

class diffPrinter extends Printer
{
void print(String s)
{
  // prints String differently
}

}
0
Arun