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?
6 réponses
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, 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.
Wikipédia fournit une bonne définition:
En programmation orientée objet, polymorphisme de sous-type ou inclusion le polymorphisme est un concept dans la théorie des types, dans lequel un nom peut désigner 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 substituable pour des objets d'un autre type (leur base type (S)) et peut donc être géré via une interface commune. Alternativement, le polymorphisme d'inclusion peut être réalisé par type coercition, également connu sous le nom de coulée de type.
Un autre article de Wikipedia appelé polymorphisme dans la programmation orientée objet semble également répondre à vos questions.
En Java
Ce sous-type fonctionnalité en Java est atteint, entre autres moyens, grâce à l'héritage des classes et des interfaces. Bien que les fonctionnalités de sous-typage de Java peuvent ne pas être évidentes en termes d'héritage tout le temps. Prenons par exemple les cas de covariance et de contravariance avec les génériques. En outre, les tableaux sont sérialisables et Clonables bien que cela ne soit évident nulle part dans la hiérarchie des types. On peut aussi dire que grâce à la conversion d'élargissement primitive, les opérateurs numériques en Java sont polymorphes, dans certains cas même accepter des opérandes totalement indépendants (c'est-à-dire concaténation de chaînes et de nombres ou d'une chaîne plus un autre objet). Considérez également les cas de boxe et de déballage des primitives. Ces derniers cas de polymorphisme (coercions et surcharge) ne sont pas du tout liés à l'héritage.
Exemples
L'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 le 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 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]
Pourtant, tout cela, 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. Le type de la liste et le type de prédicat sont paramétrique. Voir cet exemple avec des expressions lambda disponibles dans JDK 8 Preview (pour la concision 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, c'est une forme de polymorphisme universel.
La Contrainte
double sum = 1 + 2.0;
Entier et arithmétique à virgule flottante sont totalement différents. Application de l'opérateur plus à deux opérandes de types différents, ici, est impossible sans une certaine forme de coercition.
Dans cet exemple, les types integer et double, sont automatiquement contraints (convertis) en type double sans conversion explicite. L'expression entière est promue à double. En effet, en Java, nous avons des conversions d'élargissement primitives.
Selon Cardelli, cette forme de coercition automatique est une forme de polymorphisme ad hoc prévu pour l'opérateur plus.
Il y a des langues dans que vous ne pourriez même pas additionner un entier et un nombre à virgule flottante sans un cast explicite (C'est-à-dire AFAIK, SML, dans lequel, en passant, le polymorphisme paramétrique est la clé pour surmonter ce genre de problèmes).
La Surcharge
double sum = 2.0 + 3.0;
String text = "The sum is" + sum;
L'opérateur plus signifie ici deux choses différentes en fonction des arguments utilisés. De toute évidence, l'opérateur a été surchargé. Cela implique qu'il a différentes implémentations en fonction des types d'opérandes. Selon Cardelli, il s'agit d'un forme de polymorphisme ad hoc prévue 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 java.lang.Les méthodes mathématiques min et max sont surchargées pour supporter différents types primitifs).
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 Duck typing dans des langages dynamiques comme Python ou même dans des langages statiquement typés comme Go, ou types de données algébriques dans des langages comme SML, Ocaml et Scala, ou classes de type dans des langages comme Haskell, multi methods dans Clojure, héritage prototypique en JavaScript, etc.
Polymorphisme ad hoc > surcharge de L'opérateur > sans héritage
Polymorphisme ad hoc > surcharge de méthode > sans héritage
Polymorphisme ad hoc > substitution de méthode > avec Inheritence
Polymorphisme Paramétrique > Génériques > Sans Héritage
Polymorphisme de sous-type ou polymorphisme D'Inclusion > affectation polymorphe > avec héritage
Polymorphisme de sous-type ou polymorphisme D'Inclusion > Type de retour polymorphe > avec L'héritage
Polymorphisme de sous-type ou polymorphisme D'Inclusion > Type D'Argument polymorphe > avec héritage
Polymorphisme de coercition > élargissement > avec ou sans héritage
Polymorphisme de coercition > auto boxing et unboxing > sans héritage
Polymorphisme de coercition > var args > sans héritage
Polymorphisme De Coercition > Type Casting > Sans Héritage
Bien 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())
Deux x
et y
sont Foo
s, mais ils ont des résultats différents lorsque vous appelez a()
.
Type Statique
Surcharge - ce qui signifie plusieurs méthodes avec le même nom mais une signature différente qui est possible sans surcharger
class StaticPolyExample
{
void print(int s)
{
//print s
}
void print(String s)
{
//print s
}
}
Type dynamique
Overriding - 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
}
}
La surcharge de fonction est l'un des polymorphismes (bien que ce ne soit pas ce que l'on entend par polymorphisme réel) qui peut être atteint 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());
Arrestation méthode 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 polymorphisme réel en général ne peut pas être atteint sans héritage.
Oui, je pense qu'ils voulaient probablement entendre parler de 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 exspectons un objet avec un tel intervace. 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());
}
}
}