Les interfaces
Notions théoriques
Qu'est-ce qu'une interface ?
Une interface est un contrat que des classes s'engagent à respecter. Elle définit ce qu'une classe doit savoir faire, sans préciser comment elle le fait.
Contrairement à l'héritage (relation "est-un"), une interface modélise une relation "peut faire" ou "est capable de" :
- Un
Magepeut se soigner →Mage implements Soignable - Un
Guerrierpeut attaquer →Guerrier implements Attaquable
// Déclaration d'une interface
public interface Soignable {
void soigner(int points); // méthode abstraite implicitement
}
Déclarer une interface
Le mot-clé interface remplace class. Toutes les méthodes d'une interface sont publiques et abstraites par défaut : inutile d'écrire public abstract.
public interface Soignable {
void soigner(int points); // implicitement : public abstract void soigner(int points);
}
public interface Attaquable {
void attaquer(Personnage cible);
}
Implémenter une interface avec implements
Une classe déclare qu'elle respecte le contrat d'une interface avec le mot-clé implements. Elle doit alors fournir un corps à toutes les méthodes de l'interface, avec @Override.
public class Mage extends Personnage implements Soignable {
@Override
public void soigner(int points) {
if (mana >= 10) {
pointsDeVie += points;
mana -= 10;
System.out.println(nom + " se soigne de " + points + " PV !");
} else {
System.out.println(nom + " n'a pas assez de mana pour se soigner.");
}
}
}
Plusieurs interfaces à la fois
Java n'autorise qu'un seul héritage de classe (extends), mais une classe peut implémenter autant d'interfaces que nécessaire.
public class Paladin extends Personnage implements Soignable, Attaquable {
@Override
public void soigner(int points) { /* ... */ }
@Override
public void attaquer(Personnage cible) { /* ... */ }
}
De la même façon qu'une classe peut implémenter plusieurs interfaces, une interface peut hériter de plusieurs autres interfaces avec extends :
// Combattant regroupe les contrats Soignable et Attaquable
public interface Combattant extends Soignable, Attaquable {
int getNiveau(); // méthode supplémentaire propre à Combattant
}
Toute classe qui implémente Combattant doit alors fournir soigner(), attaquer() et getNiveau().
L'interface comme type
Une interface peut être utilisée comme type de variable. C'est très utile pour écrire du code générique.
Soignable s = new Mage("Elara", 80, 10, 100);
s.soigner(20); // fonctionne car Mage implements Soignable
// Mais :
s.attaquer(cible); // ERREUR : le type Soignable ne connaît pas attaquer()
Méthodes default (Java 8+)
Depuis Java 8, une interface peut contenir des méthodes avec un corps grâce au mot-clé default. Cela permet d'ajouter de nouveaux comportements sans casser les classes existantes.
public interface Soignable {
void soigner(int points);
// Méthode default : implémentation fournie, les classes peuvent la redéfinir
default void soignerCompletement() {
soigner(9999);
System.out.println("Soin total !");
}
}
Méthodes static dans une interface
Une interface peut aussi contenir des méthodes statiques utilitaires.
public interface Soignable {
void soigner(int points);
static int calculerSoin(int niveau) {
return niveau * 10;
}
}
// Appel :
int soin = Soignable.calculerSoin(5); // → 50
Constantes dans une interface
Les attributs déclarés dans une interface sont implicitement public static final (des constantes).
public interface Attaquable {
int DEGATS_MINIMUM = 1; // implicitement : public static final int DEGATS_MINIMUM = 1;
void attaquer(Personnage cible);
}
Exemple pratique
// Fichier : Soignable.java
public interface Soignable {
void soigner(int points);
default void soignerCompletement() {
soigner(Integer.MAX_VALUE);
System.out.println("Soin total appliqué !");
}
}
// Fichier : Attaquable.java
public interface Attaquable {
int DEGATS_MINIMUM = 1;
void attaquer(Personnage cible);
}
// Fichier : Mage.java
public class Mage extends Personnage implements Soignable {
int mana;
public Mage(String nom, int pointsDeVie, int force, int mana) {
super(nom, pointsDeVie, force);
this.mana = mana;
}
@Override
public void attaquer(Personnage cible) {
if (mana >= 15) {
int degats = Math.max(force * 3, Attaquable.DEGATS_MINIMUM);
cible.pointsDeVie -= degats;
mana -= 15;
System.out.println(nom + " lance un sort ! Dégâts : " + degats);
} else {
cible.pointsDeVie -= force;
System.out.println(nom + " frappe faiblement.");
}
}
@Override
public void soigner(int points) {
if (mana >= 10) {
pointsDeVie += points;
mana -= 10;
System.out.println(nom + " se soigne de " + points + " PV ! PV actuels : " + pointsDeVie);
} else {
System.out.println(nom + " n'a pas assez de mana pour se soigner.");
}
}
}
// Fichier : Main.java
public class Main {
public static void main(String[] args) {
Mage mage = new Mage("Elara", 80, 12, 50);
// Utiliser le Mage via l'interface Soignable
Soignable soigneur = mage;
soigneur.soigner(30);
// Utiliser la méthode default
soigneur.soignerCompletement(); // méthode default de l'interface
}
}
Soignable soigneur = mage; illustre l'utilisation de l'interface comme type. La variable soigneur ne "voit" que les méthodes définies dans Soignable. C'est le polymorphisme par interface.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Vous allez créer deux interfaces Soignable et Attaquable, puis les implémenter dans les classes du RPG.
Étape 1 — Créer l'interface Soignable
Créez le fichier Soignable.java avec la méthode soigner(int points) et une méthode default afficherPV().
Une interface doit représenter un seul comportement cohérent. Préférez plusieurs petites interfaces (Soignable, Attaquable) à une grande interface fourre-tout. C'est le principe de ségrégation des interfaces (Interface Segregation Principle).
Étape 2 — Implémenter Soignable dans Mage
Modifiez Mage pour implémenter l'interface Soignable. La méthode soigner() coûte 10 de mana et restaure les points de vie.
L'annotation @Override s'applique aussi quand on implémente une méthode d'interface, pas seulement quand on redéfinit une méthode héritée. Elle garantit que la signature correspond exactement à celle de l'interface.
Étape 3 — Utiliser l'interface comme type dans Main
Dans Main, déclarez une variable de type Soignable et appelez la méthode soigner().
Écrire Soignable soigneur = new Mage(...) plutôt que Mage soigneur = new Mage(...) est une bonne pratique : le code qui utilise soigneur ne dépend pas de la classe concrète. Si vous remplacez Mage par Pretre (qui implémente aussi Soignable), le reste du code reste inchangé.