<!--
https://informatecdigital.com/fr/Polymorphisme-en-programmation-orient%C3%A9e-objet/
-->
Quand le comportement d’un objet dépend de son type : des façons différentes de répondre à la même question.
- Polymorphisme ad hoc (**surcharge**)
- Polymorphisme par **sous-typage** (**concrétisation** / **redefinition**)
- Héritage d'interface
- Héritage d'implémentation
- Polymorphisme paramétré (types génériques )
## Polymorphisme ad hoc (surcharge)
Le polymorphisme ad hoc (ou surcharge, ou « overload ») permet l’implémentation de fonctions **avec le même nom** pouvant prendre **des paramètres de type différents**.
L’utilisation de la fonction est différente en fonction du type passé en paramètre.
### Surcharge de méthode
La surcharge (overloading) d'une méthode : plusieurs signatures pour une méthode portant le même nom.
Autrement dit, une méthode portant le même nom avec des paramètres différents.
> [!example] Surcharge de la méthode `valueOf()` de String
> ```java
> class Integer{
>
> public static String valueOf(Object obj) {
> return (obj == null) ? "null" : obj.toString();
> }
> public static String valueOf(char[] data) {
> return new String(data);
> }
> public static String valueOf(boolean b) {
> return b ? "true" : "false";
> }
> // ...
> }
> ```
> [!tip] La surcharge est également possible pour les constructeurs !
Le type, le nombre ou l'ordre des paramètres doit changer.
```java
class Calculatrice {
public int additionner(int a, int b) {
return a + b;
}
public double additionner(double a, double b) {
return a + b;
}
}
```
Ici, la méthode additionner est définie pour deux types de paramètres différents (int et double).
> [!warning] le nom des paramètres est ignoré.
>
>
> ```java
> void afficher(String nom){}
>
> // ne compile pas car afficher(String) est considéré comme la même signature
> void afficher(String adresse){}
> ```
> [!tip] Envelopper dans un type
> En enveloppant la valeur dans un type, on peut indiquer au compilateur que `nom` et `Adresse` sont des types différents.
>
> ```java
> void afficher(String nom){}
>
> record Adresse(String value){}
>
> void afficher(Adresse adresse){}
> ```
### Paramètres variables pour une méthode
Il est possible de surcharger une methode en lui donnant un nombre arbitraire de paramètres.
`String... names` est équivalent à `String[] names`.
Et permet de d'appeler une méthode ainsi.
```java
public static String greet(String name) {
return "Hello " + name + "!";
}
// Arguments variables
public static String greet(String... names) {
if (names.length == 0) {
return greet("World");
}
return "Salut " + String.join(", ", names) + "!";
}
```
Que donnera l'appel suivant ?
```java
IO.println(greet());
IO.println(greet("Toi"));
// On peut appeler un nombre arbitraire d'arguments
IO.println(greet("You", "Me", "Everybody"));
```
> [!note]- Réponse
> ```text
> Hello World!
> Hello Toi!
> Salut You, Me, Everybody!
> ```
## Polymorphisme par sous-typage
Le **sous-typage** permet de transmettre la **signature** de type à des sous-types.
On peut ainsi **invoquer une méthode** avec des paramètres dont les types sont des **sous-types** des types attendus.
Ex. on définit un type parent pouvant ajouter un cadeau et retirer l'ensemble des cadeaux
```java
public interface Conteneur{
void ajouterCadeau(String cadeau);
Iterable<String> retirerCadeaux();
}
```
On crée des variantes de conteneur : Sac
```java
public class Sac implements Conteneur{
@Override
void ajouterCadeau(String cadeau){
// ...
}
@Override
Iterable<String> retirerCadeaux(){
// ...
}
}
```
Hotte
```java
public class Hotte implements Conteneur{
@Override
void ajouterCadeau(String cadeau){
// ...
}
@Override
Iterable<String> retirerCadeaux(){
// ...
}
}
```
Le traineau du pere noel peut être chargé depuis des conteneurs (Sac, Hotte, ou tout autre sous-type de Conteneur).
```java
public class TraineauDuPereNoel{
private List<String> stockage = new ArrayList<String>();
public void charger(Conteneur conteneur){
stockage.addAll(conteneur.retirerCadeaux());
}
}
```
```java
public class Main {
public static void main(){
Sac sac = new Sac();
sac.ajouterCadeau("Poupée");
sac.ajouterCadeau("Camion");
sac.ajouterCadeau("Pokedex");
Hotte hotte = new Hotte();
hotte.ajouterCadeau("Playstation");
hotte.ajouterCadeau("Lego");
hotte.ajouterCadeau("Livre");
var traineau = new TraineauDuPereNoel();
traineau.charger(sac);
traineau.charger(hotte);
}
}
```
### Sous-typage par interface
Une interface décrit la signatures de méthodes publiques que devront implémenter les classes qui la réalisent.
Une interface ne décrit pas les données ou les mécanismes internes de l'implémentation.
```mermaid
classDiagram
direction BT
class Forme
<<interface>> Forme
Forme: + aire() float
class Carre {
- cote : float
+ aire() float
}
Carre ..|> Forme
class Cercle{
- rayon : float
+ aire() float
}
Cercle ..|> Forme
```
```java
public interface Forme {
float aire();
}
```
Quand une classe implémente une interface, elle **doit** implémenter les comportements définis dans l'interface.
Leur concrétisation se fait par le mécanisme de redéfinition (override) de java.
```java
public class Carre implements Forme {
private final float cote;
public Carre(float cote) {
this.cote = cote;
}
@Override
public float aire() {
return cote * cote;
}
}
public class Cercle implements Forme {
private final float rayon;
public Cercle(float rayon) {
this.rayon = rayon;
}
@Override
public float aire() {
return (float) Math.PI * rayon * rayon;
}
}
```
```java
public interface Nageur{
void nager();
}
public class Canard implemets Nageur {
//…
}
public class CanardColVert is extends Canard {
//…
}
```
```java
Nageur n1 = new Canard();
Nageur n2 = new CanardColVert();
n2.nager();
n1.nager();
```
Voir [[Héritage de type]]
### Sous-typage par héritage d'implémentation
En plus de permettre de transmettre des **comportements** et des **données**, l'héritage d'implémentation transmet également **ses types**.
```mermaid
classDiagram
class A{
data : String
+ foo() String
}
class B{
data : String
+ bar() String
+ foo() String
}
A <|-- B
```
```java
class A{
protected String data="data";
String foo(){
return "Foo"
};
}
class B extends A {
String bar(){
return super.foo().toUpperCase();
}
}
```
```java
A a = new A()
a.foo();
B b = new B()
b.bar();
b.foo();
A[] array = { a, b};
for( item : array){
item.foo();
}
```
Voir [[Héritages en Java]]
#### Classes abstraites
Si une **[[Glossaire général#Classe abstraite|classe abstraite]]** est **partiellement implémentée** et ne **peut pas être instanciée**, son type peut être utilisé dans la déclaration de variable.
Les méthodes abstraites doivent être concrétisées par le mécanisme de redéfinition (override) de java.
```java
public abstract class Forme {
// ...
}
public class Carre extends Forme {
// ...
}
public class Cercle extends Forme {
// ...
}
```
```java
Forme[] tableau = { new Carre(), new Cercle(), new Carre() };
```
Voir [[Glossaire général#Héritage d’implémentation]]
## Polymorphisme paramétré
Permet de définir des traitements qui peuvent être appliqués à des **types paramétrés** (ou "[[Glossaire général#Type générique|types génériques]]" en Java).
```mermaid
classDiagram
class List~T~{
add(~T~ item)
}
```
En Java les types paramétrés sont appelés "[Generic Types](https://dev.java/learn/generics/)"
> [!example] `List<T>` en Java
> En Java, les collections sont des types génériques. Les opérations sur des instances de collections peuvent s'appliquer quel que soit le type de données qu'elle contiennent. Le paramètre de type de la collection doit être donné lors de sa définition :
>
> ```java
> List<String> strings = new ArrayList<String>();
> strings.add("Foo");
> strings.add("Bar");
>
> List<Integer> ints = new ArrayList<Integer>();
> ints.add(1);
> ints.add(2);
> ```
Les types génériques sont un concept avancé qui ne sera pas approfondi dans ce cours.
## À suivre
[[Gestion des dépendances et déploiement]]