Aller au contenu principal

4) Utiliser des interfaces

Interfaces et polymorphisme

Nos guerriers attaquent et nos mages attaquent. Mais certains personnages peuvent aussi soigner leurs alliés. Une interface permet de définir ce contrat de soin, indépendamment de la hiérarchie d'héritage existante.

Objectifs de la séance

  • Comprendre la différence entre classe abstraite et interface
  • Créer l'interface Soignable et l'implémenter dans Mage
  • Stocker des objets de types différents dans une même List<Personnage>
  • Utiliser instanceof et le pattern matching Java 16 pour identifier le type réel

Notions théoriques

Qu'est-ce qu'une interface ?

Une interface définit un contrat : elle liste des méthodes que toute classe qui l'implémente doit fournir. Elle ne contient pas d'attributs d'instance ni de constructeur.

public interface Soignable {
void soigner(int points);
}
info

Depuis Java 8, une interface peut avoir des méthodes default avec un corps. Mais pour les débutants, pensez d'abord aux interfaces comme à des contrats de méthodes abstraites.

implements pour implémenter une interface

public class Mage extends Personnage implements Soignable {

private int mana;

// ... constructeur et attaquer() ...

@Override
public void soigner(int points) {
if (mana >= 5) {
setPointsDeVie(getPointsDeVie() + points);
mana -= 5;
System.out.println(getNom() + " se soigne de " + points + " PV !");
} else {
System.out.println(getNom() + " n'a pas assez de mana pour se soigner.");
}
}
}
attention

Une classe peut hériter d'une seule classe parente (extends) mais peut implémenter plusieurs interfaces (implements A, B, C). C'est la façon dont Java gère l'héritage multiple.

Polymorphisme — liste hétérogène

Le polymorphisme signifie qu'un objet Guerrier peut être référencé par une variable de type Personnage. Cela permet de stocker des guerriers et des mages dans la même liste.

List<Personnage> equipe = new ArrayList<>();
equipe.add(new Guerrier("Aragorn", 150, 30));
equipe.add(new Mage("Gandalf", 100, 20, 50));

for (Personnage p : equipe) {
p.attaquer(autreCible); // appelle la bonne méthode selon le type réel
}

instanceof et pattern matching (Java 16+)

Pour identifier le type réel d'un objet et accéder à ses méthodes spécifiques :

for (Personnage p : equipe) {
// Ancienne syntaxe (avant Java 16)
if (p instanceof Mage) {
Mage m = (Mage) p;
m.soigner(20);
}

// Nouvelle syntaxe — pattern matching (Java 16+)
if (p instanceof Mage m) {
m.soigner(20);
}
}
info

Le pattern matching p instanceof Mage m teste le type et crée automatiquement la variable m déjà castée. C'est plus concis et élimine le risque de ClassCastException.

Exemple pratique

import java.util.ArrayList;
import java.util.List;

public class Main {

public static void main(String[] args) {
List<Personnage> equipe = new ArrayList<>();
equipe.add(new Guerrier("Aragorn", 150, 30));
equipe.add(new Mage("Gandalf", 80, 20, 50));
equipe.add(new Mage("Saruman", 90, 22, 60));

Personnage ennemi = new Guerrier("Ennemi", 200, 15);

System.out.println("=== Tour d'attaque ===");
for (Personnage p : equipe) {
p.attaquer(ennemi);
}

System.out.println("\n=== Phase de soin ===");
for (Personnage p : equipe) {
if (p instanceof Mage m) {
m.soigner(30);
}
}

System.out.println("\n=== État de l'équipe ===");
for (Personnage p : equipe) {
p.afficher();
}
}
}

Test de mémorisation/compréhension


Quel mot-clé utilise-t-on pour qu'une classe respecte un contrat d'interface ?


Combien de classes parentes une classe Java peut-elle avoir ?


Que permet le polymorphisme dans une List<Personnage> ?


Que fait `if (p instanceof Mage m)` en Java 16+ ?


Une interface peut-elle contenir des attributs d'instance ?


TP pour réfléchir et résoudre des problèmes

Vous allez créer l'interface Soignable, l'implémenter dans Mage, puis écrire une boucle de combat avec une liste hétérogène.

Étape 1 — Créer l'interface Soignable

Créez Soignable.java avec la méthode soigner(int points).


Bonne pratique - Une interface = un contrat précis

Nommez vos interfaces avec un adjectif ou un participe qui décrit la capacité (Soignable, Comparable, Serializable). C'est la convention Java. Cela rend le code lisible : Mage implements Soignable se lit naturellement comme "un Mage est soignable".

Étape 2 — Implémenter soigner() dans Mage

Ajoutez implements Soignable à Mage et implémentez soigner(int points) : si le mage a au moins 5 mana, il augmente ses propres PV et consomme 5 mana.


Bonne pratique - Vérifier les préconditions avant d'agir

Toujours vérifier les conditions nécessaires (ici : avoir assez de mana) avant d'effectuer une action. Cela évite des états incohérents (mana négatif, PV abusifs) et rend le comportement du jeu prévisible.

Étape 3 — Boucle de combat avec liste hétérogène

Dans Main, créez une liste List<Personnage> contenant un Guerrier et deux Mage. Faites attaquer tout le monde un ennemi, puis faites soigner les mages.


Bonne pratique - Pattern matching plutôt que cast explicite

Préférez if (p instanceof Mage m) à if (p instanceof Mage) { Mage m = (Mage) p; }. La version moderne est plus courte, élimine le cast redondant et est impossible à rater (vous ne pouvez pas oublier de caster).

📌 Une solution